diff --git a/client/src/main/java/bean/ColourPicker.java b/client/src/main/java/bean/ColourPicker.java deleted file mode 100644 index a1ffc5b..0000000 --- a/client/src/main/java/bean/ColourPicker.java +++ /dev/null @@ -1,99 +0,0 @@ -package bean; - -import java.awt.Color; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.image.BufferedImage; - -import javax.swing.ImageIcon; -import javax.swing.JLabel; -import javax.swing.border.LineBorder; - -import screen.ColourChooserDialog; - -public class ColourPicker extends JLabel - implements MouseListener -{ - //Static so that 'recent colours' get remembered across different ColourPicker beans - private static final ColourChooserDialog dlg = new ColourChooserDialog(); - - private ColourSelectionListener listener = null; - private Color selectedColour = null; - private BufferedImage img = null; - - public ColourPicker() - { - super(); - setBorder(new LineBorder(new Color(0, 0, 0))); - setSize(30, 20); - setOpaque(true); - - addMouseListener(this); - } - - public void addColourSelectionListener(ColourSelectionListener listener) - { - this.listener = listener; - } - - public Color getSelectedColor() - { - return selectedColour; - } - public void setSelectedColor(Color colour) - { - if (colour == null - || colour.equals(selectedColour)) - { - return; - } - - selectedColour = colour; - - int width = getWidth(); - int height = getHeight(); - - if (img == null) - { - img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - } - - for (int x=0; x -{ - public static final String FILTER_MODE_EQUAL_TO = "="; - public static final String FILTER_MODE_GREATER_THAN = ">"; - public static final String FILTER_MODE_LESS_THAN = "<"; - - private final String[] filterModes = {FILTER_MODE_EQUAL_TO, FILTER_MODE_GREATER_THAN, FILTER_MODE_LESS_THAN}; - - public ComboBoxNumberComparison() - { - super(); - setModel(comboModel); - setPreferredSize(new Dimension(40, 30)); - setMaximumSize(new Dimension(40, 30)); - } - - private final DefaultComboBoxModel comboModel = new DefaultComboBoxModel<>(filterModes); - - public void addOption(String option) - { - comboModel.addElement(option); - } -} diff --git a/client/src/main/java/bean/FileUploader.java b/client/src/main/java/bean/FileUploader.java index 029b0b5..4e1caba 100644 --- a/client/src/main/java/bean/FileUploader.java +++ b/client/src/main/java/bean/FileUploader.java @@ -24,11 +24,7 @@ public class FileUploader extends JPanel { private File selectedFile = null; private ArrayList listeners = new ArrayList<>(); - - public FileUploader(FileFilter ff) - { - this(ff, "Upload"); - } + public FileUploader(FileFilter ff, String buttonName) { setLayout(new BorderLayout(0, 0)); diff --git a/client/src/main/java/bean/RadioButtonPanel.java b/client/src/main/java/bean/RadioButtonPanel.java deleted file mode 100644 index 5e3e0d0..0000000 --- a/client/src/main/java/bean/RadioButtonPanel.java +++ /dev/null @@ -1,85 +0,0 @@ -package bean; - -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.Enumeration; - -import javax.swing.AbstractButton; -import javax.swing.ButtonGroup; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -public class RadioButtonPanel extends JPanel - implements ChangeListener -{ - private ButtonGroup bg = new ButtonGroup(); - private JRadioButton selection = null; - - @Override - public Component add(Component arg0) - { - if (arg0 instanceof JRadioButton) - { - JRadioButton rdbtn = (JRadioButton)arg0; - rdbtn.addChangeListener(this); - - bg.add(rdbtn); - if (bg.getButtonCount() == 1) - { - //Ensure this is selected - rdbtn.setSelected(true); - } - } - - return super.add(arg0); - } - - public JRadioButton getSelection() - { - return selection; - } - public String getSelectionStr() - { - return selection.getText(); - } - - public void addActionListener(ActionListener listener) - { - Enumeration buttons = bg.getElements(); - while (buttons.hasMoreElements()) - { - AbstractButton button = buttons.nextElement(); - button.addActionListener(listener); - } - } - - public boolean isEventSource(ActionEvent evt) - { - Object source = evt.getSource(); - - Enumeration buttons = bg.getElements(); - while (buttons.hasMoreElements()) - { - AbstractButton button = buttons.nextElement(); - if (source == button) - { - return true; - } - } - - return false; - } - - @Override - public void stateChanged(ChangeEvent arg0) - { - JRadioButton src = (JRadioButton)arg0.getSource(); - if (src.isSelected()) - { - selection = src; - } - } -} diff --git a/client/src/main/java/bean/RadioImage.java b/client/src/main/java/bean/RadioImage.java deleted file mode 100644 index ed657bc..0000000 --- a/client/src/main/java/bean/RadioImage.java +++ /dev/null @@ -1,72 +0,0 @@ -package bean; - -import java.awt.Color; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; - -import javax.swing.ButtonGroup; -import javax.swing.ImageIcon; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.border.EmptyBorder; -import javax.swing.border.LineBorder; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; - -public class RadioImage extends JPanel - implements ChangeListener, - MouseListener -{ - public RadioImage(ImageIcon img) - { - setBorder(new EmptyBorder(1, 1, 1, 1)); - lblImg.setIcon(img); - - add(rdbtn); - add(lblImg); - - rdbtn.addChangeListener(this); - lblImg.addMouseListener(this); - } - - private final JRadioButton rdbtn = new JRadioButton(); - private final JLabel lblImg = new JLabel(); - - public void addToButtonGroup(ButtonGroup bg) - { - bg.add(rdbtn); - } - - public boolean isSelected() - { - return rdbtn.isSelected(); - } - - @Override - public void stateChanged(ChangeEvent arg0) - { - if (rdbtn.isSelected()) - { - setBorder(new LineBorder(Color.BLACK)); - } - else - { - setBorder(new EmptyBorder(1, 1, 1, 1)); - } - } - - @Override - public void mouseClicked(MouseEvent arg0) - { - rdbtn.setSelected(true); - } - @Override - public void mouseEntered(MouseEvent arg0){} - @Override - public void mouseExited(MouseEvent arg0){} - @Override - public void mousePressed(MouseEvent arg0){} - @Override - public void mouseReleased(MouseEvent arg0){} -} diff --git a/client/src/main/java/bean/ScrollTableButton.java b/client/src/main/java/bean/ScrollTableButton.java deleted file mode 100644 index 070d611..0000000 --- a/client/src/main/java/bean/ScrollTableButton.java +++ /dev/null @@ -1,23 +0,0 @@ -package bean; - -import javax.swing.Action; -import javax.swing.table.DefaultTableModel; - -public class ScrollTableButton extends ScrollTable -{ - private int buttonColumn = -1; - - public ScrollTableButton(int buttonColumn, DefaultTableModel tm, Action a) - { - this.buttonColumn = buttonColumn; - - setModel(tm); - ButtonColumn bc = new ButtonColumn(this, a, buttonColumn); - } - - @Override - public boolean isEditable(int row, int col) - { - return col == buttonColumn; - } -} diff --git a/client/src/main/java/bean/SuperTextPane.java b/client/src/main/java/bean/SuperTextPane.java deleted file mode 100644 index 76604bc..0000000 --- a/client/src/main/java/bean/SuperTextPane.java +++ /dev/null @@ -1,60 +0,0 @@ -package bean; - -import javax.swing.JTextPane; -import javax.swing.text.AttributeSet; -import javax.swing.text.BadLocationException; -import javax.swing.text.DefaultStyledDocument; -import javax.swing.text.SimpleAttributeSet; -import javax.swing.text.StyleConstants; - -import util.Debug; - -public class SuperTextPane extends JTextPane -{ - public SuperTextPane() - { - super(); - - setDocument(doc); - } - - private AwesomeStyledDocument doc = new AwesomeStyledDocument(); - - /** - * Append text - */ - public void append(String str) - { - append(str, false); - } - public void append(String str, boolean bold) - { - append(str, bold, false); - } - public void append(String str, boolean bold, boolean italic) - { - SimpleAttributeSet style = new SimpleAttributeSet(); - StyleConstants.setBold(style, bold); - StyleConstants.setItalic(style, italic); - - doc.append(str, style); - } - - /** - * Overridden document with helper methods - */ - private static final class AwesomeStyledDocument extends DefaultStyledDocument - { - public void append(String str, AttributeSet style) - { - try - { - insertString(getLength(), str, style); - } - catch (BadLocationException ble) - { - Debug.stackTrace(ble); - } - } - } -} diff --git a/client/src/main/java/bean/TableButtonListener.java b/client/src/main/java/bean/TableButtonListener.java deleted file mode 100644 index c6adcfd..0000000 --- a/client/src/main/java/bean/TableButtonListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package bean; - -public interface TableButtonListener -{ - public void tableButtonPressed(); -} diff --git a/client/src/main/java/bean/WrapLayout.java b/client/src/main/java/bean/WrapLayout.java deleted file mode 100644 index d68cfc4..0000000 --- a/client/src/main/java/bean/WrapLayout.java +++ /dev/null @@ -1,195 +0,0 @@ -package bean; - -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Insets; - -import javax.swing.JScrollPane; -import javax.swing.SwingUtilities; - -/** - * FlowLayout subclass that fully supports wrapping of components. - */ -public class WrapLayout extends FlowLayout -{ - /** - * Constructs a new WrapLayout with a left - * alignment and a default 5-unit horizontal and vertical gap. - */ - public WrapLayout() - { - super(); - } - - /** - * Constructs a new FlowLayout with the specified - * alignment and a default 5-unit horizontal and vertical gap. - * The value of the alignment argument must be one of - * WrapLayout, WrapLayout, - * or WrapLayout. - * @param align the alignment value - */ - public WrapLayout(int align) - { - super(align); - } - - /** - * Creates a new flow layout manager with the indicated alignment - * and the indicated horizontal and vertical gaps. - *

- * The value of the alignment argument must be one of - * WrapLayout, WrapLayout, - * or WrapLayout. - * @param align the alignment value - * @param hgap the horizontal gap between components - * @param vgap the vertical gap between components - */ - public WrapLayout(int align, int hgap, int vgap) - { - super(align, hgap, vgap); - } - - /** - * Returns the preferred dimensions for this layout given the - * visible components in the specified target container. - * @param target the component which needs to be laid out - * @return the preferred dimensions to lay out the - * subcomponents of the specified container - */ - @Override - public Dimension preferredLayoutSize(Container target) - { - return layoutSize(target, true); - } - - /** - * Returns the minimum dimensions needed to layout the visible - * components contained in the specified target container. - * @param target the component which needs to be laid out - * @return the minimum dimensions to lay out the - * subcomponents of the specified container - */ - @Override - public Dimension minimumLayoutSize(Container target) - { - Dimension minimum = layoutSize(target, false); - minimum.width -= (getHgap() + 1); - return minimum; - } - - /** - * Returns the minimum or preferred dimension needed to layout the target - * container. - * - * @param target target to get layout size for - * @param preferred should preferred size be calculated - * @return the dimension to layout the target container - */ - private Dimension layoutSize(Container target, boolean preferred) - { - synchronized (target.getTreeLock()) - { - // Each row must fit with the width allocated to the containter. - // When the container width = 0, the preferred width of the container - // has not yet been calculated so lets ask for the maximum. - - int targetWidth = target.getSize().width; - Container container = target; - - while (container.getSize().width == 0 && container.getParent() != null) - { - container = container.getParent(); - } - - targetWidth = container.getSize().width; - - if (targetWidth == 0) - targetWidth = Integer.MAX_VALUE; - - int hgap = getHgap(); - int vgap = getVgap(); - Insets insets = target.getInsets(); - int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2); - int maxWidth = targetWidth - horizontalInsetsAndGap; - - // Fit components into the allowed width - - Dimension dim = new Dimension(0, 0); - int rowWidth = 0; - int rowHeight = 0; - - int nmembers = target.getComponentCount(); - - for (int i = 0; i < nmembers; i++) - { - Component m = target.getComponent(i); - - if (m.isVisible()) - { - Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize(); - - // Can't add the component to current row. Start a new row. - - if (rowWidth + d.width > maxWidth) - { - addRow(dim, rowWidth, rowHeight); - rowWidth = 0; - rowHeight = 0; - } - - // Add a horizontal gap for all components after the first - - if (rowWidth != 0) - { - rowWidth += hgap; - } - - rowWidth += d.width; - rowHeight = Math.max(rowHeight, d.height); - } - } - - addRow(dim, rowWidth, rowHeight); - - dim.width += horizontalInsetsAndGap; - dim.height += insets.top + insets.bottom + vgap * 2; - - // When using a scroll pane or the DecoratedLookAndFeel we need to - // make sure the preferred size is less than the size of the - // target containter so shrinking the container size works - // correctly. Removing the horizontal gap is an easy way to do this. - - Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target); - - if (scrollPane != null && target.isValid()) - { - dim.width -= (hgap + 1); - } - - return dim; - } - } - - /** - * A new row has been completed. Use the dimensions of this row - * to update the preferred size for the container. - * - * @param dim update the width and height when appropriate - * @param rowWidth the width of the row to add - * @param rowHeight the height of the row to add - */ - private void addRow(Dimension dim, int rowWidth, int rowHeight) - { - dim.width = Math.max(dim.width, rowWidth); - - if (dim.height > 0) - { - dim.height += getVgap(); - } - - dim.height += rowHeight; - } -} diff --git a/client/src/main/java/online/screen/GameRoom.java b/client/src/main/java/online/screen/GameRoom.java index cdd08c2..baf68a6 100644 --- a/client/src/main/java/online/screen/GameRoom.java +++ b/client/src/main/java/online/screen/GameRoom.java @@ -706,7 +706,7 @@ public void startGame(int personToStart) } catch (Throwable t) { - Debug.stackTraceNoError(t); + logger.error("replayClearError", "Error clearing replay", t); } adjustPlayersBasedOnHands(); diff --git a/client/src/main/java/online/util/XmlBuilderClient.java b/client/src/main/java/online/util/XmlBuilderClient.java index 436e078..7dde4fe 100644 --- a/client/src/main/java/online/util/XmlBuilderClient.java +++ b/client/src/main/java/online/util/XmlBuilderClient.java @@ -17,10 +17,7 @@ import util.XmlUtil; public class XmlBuilderClient implements XmlConstants -{ - public static final int CHAT_REQUEST_POLL_MILLIS = 1000; - public static final int LOBBY_REQUEST_POLL_MILLIS = 500; - +{ /** * Used for symmetricKey requests */ diff --git a/client/src/main/java/screen/ColourChooserDialog.java b/client/src/main/java/screen/ColourChooserDialog.java deleted file mode 100644 index c2ad32f..0000000 --- a/client/src/main/java/screen/ColourChooserDialog.java +++ /dev/null @@ -1,71 +0,0 @@ -package screen; - -import java.awt.BorderLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -import javax.swing.JButton; -import javax.swing.JColorChooser; -import javax.swing.JDialog; -import javax.swing.JPanel; -import javax.swing.colorchooser.DefaultColorSelectionModel; - -import util.Debug; - -public class ColourChooserDialog extends JDialog - implements ActionListener -{ - private Color initialColour = null; - private Color selectedColour = null; - - public ColourChooserDialog() - { - setTitle("Choose Colour"); - setSize(660, 450); - getContentPane().add(colourChooser); - JPanel panel = new JPanel(); - getContentPane().add(panel, BorderLayout.SOUTH); - panel.add(btnOk); - panel.add(btnCancel); - - btnOk.addActionListener(this); - btnCancel.addActionListener(this); - } - - private final JColorChooser colourChooser = new JColorChooser(new DefaultColorSelectionModel()); - private final JButton btnOk = new JButton("Ok"); - private final JButton btnCancel = new JButton("Cancel"); - - @Override - public void actionPerformed(ActionEvent e) - { - Component source = (Component)e.getSource(); - if (source == btnOk) - { - selectedColour = colourChooser.getColor(); - dispose(); - } - else if (source == btnCancel) - { - selectedColour = initialColour; - dispose(); - } - else - { - Debug.stackTrace("Unexpected actionPerformed"); - } - } - - public Color getSelectedColour() - { - return selectedColour; - } - - public void setInitialColour(Color initialColour) - { - this.initialColour = initialColour; - colourChooser.setColor(initialColour); - } -} diff --git a/client/src/main/java/screen/TableModelDialog.java b/client/src/main/java/screen/TableModelDialog.java deleted file mode 100644 index 3bb9f84..0000000 --- a/client/src/main/java/screen/TableModelDialog.java +++ /dev/null @@ -1,60 +0,0 @@ -package screen; - -import java.awt.BorderLayout; - -import javax.swing.table.DefaultTableModel; - -import bean.ScrollTable; - -/** - * Simple dialog to show a table - */ -public class TableModelDialog extends SimpleDialog -{ - public TableModelDialog(String title, DefaultTableModel model) - { - table.setModel(model); - - setTitle(title); - setSize(600, 400); - setModal(true); - - getContentPane().add(table, BorderLayout.CENTER); - } - public TableModelDialog(String title, ScrollTable table) - { - this.table = table; - - setTitle(title); - setSize(600, 400); - setModal(true); - - getContentPane().add(table, BorderLayout.CENTER); - } - - private ScrollTable table = new ScrollTable(); - - /** - * Configure things about the table - */ - public void setColumnWidths(String colWidthsStr) - { - table.setColumnWidths(colWidthsStr); - } - public ScrollTable getTable() - { - return table; - } - - @Override - public void okPressed() - { - dispose(); - } - - @Override - public boolean allowCancel() - { - return false; - } -} \ No newline at end of file diff --git a/client/src/main/java/util/ComponentUtil.java b/client/src/main/java/util/ComponentUtil.java deleted file mode 100644 index b86a671..0000000 --- a/client/src/main/java/util/ComponentUtil.java +++ /dev/null @@ -1,70 +0,0 @@ -package util; - -import java.awt.Component; -import java.awt.Container; - -import javax.swing.AbstractButton; -import javax.swing.ButtonGroup; - -import object.HandyArrayList; - -public class ComponentUtil -{ - /** - * Recurses through all child components, returning an ArrayList of all children of the appropriate type - */ - public static HandyArrayList getAllChildComponentsForType(Container parent, Class desiredClazz) - { - HandyArrayList ret = new HandyArrayList<>(); - - Component[] components = parent.getComponents(); - addComponents(ret, components, desiredClazz); - - return ret; - } - private static void addComponents(HandyArrayList ret, Component[] components, Class desiredClazz) - { - for (int i=0; i clazz = component.getClass(); - HandyArrayList list = getAllChildComponentsForType(parent, clazz); - return list.contains(component); - } - - public static void createButtonGroup(AbstractButton... buttons) - { - if (buttons.length == 0) - { - Debug.stackTrace("Trying to create empty ButtonGroup."); - return; - } - - ButtonGroup bg = new ButtonGroup(); - for (int i=0; i extends ArrayList -{ - public HandyArrayList() - {} - public HandyArrayList(Collection collection) - { - super(collection); - } - - public E firstElement() - { - return get(0); - } - public E lastElement() - { - return get(size() - 1); - } - - public HandyArrayList factoryCopy() - { - HandyArrayList ret = new HandyArrayList<>(); - for (E element : this) - { - ret.add(element); - } - - return ret; - } - - public HandyArrayList createFilteredCopy(Predicate p) - { - Stream filteredStream = stream().filter(p); - Collector> collector = Collectors.toCollection(HandyArrayList::new); - - return filteredStream.collect(collector); - } - - @SafeVarargs - public static HandyArrayList factoryAdd(X... elements) - { - HandyArrayList ret = new HandyArrayList<>(); - - for (int i=0; i (ffs - this shouldn't be this hard!!) - E elt = firstElement(); - Class clz = (Class)elt.getClass(); - - //Now we can construct our E[] array, although even with this you have to cast and suppress warnings - E[] template = (E[])Array.newInstance(clz, size()); - - //Finally... - return toArray(template); - } - - public HandyArrayList reverse() - { - HandyArrayList ret = new HandyArrayList<>(); - for (int i=size()-1; i>=0; i--) - { - E obj = get(i); - ret.add(obj); - } - - return ret; - } - - public static HandyArrayList> getBatches(HandyArrayList list, int batchSize) - { - HandyArrayList> ret = new HandyArrayList<>(); - HandyArrayList currentBatch = new HandyArrayList<>(); - for (int i=0; i(); - currentBatch.add(item); - } - } - - ret.add(currentBatch); - return ret; - } -} diff --git a/core/src/main/java/object/HandyArrayListCollector.java b/core/src/main/java/object/HandyArrayListCollector.java deleted file mode 100644 index 3b7256a..0000000 --- a/core/src/main/java/object/HandyArrayListCollector.java +++ /dev/null @@ -1,6 +0,0 @@ -package object; - -public class HandyArrayListCollector -{ - -} diff --git a/core/src/main/java/object/HashMapCount.java b/core/src/main/java/object/HashMapCount.java deleted file mode 100644 index c8bfda4..0000000 --- a/core/src/main/java/object/HashMapCount.java +++ /dev/null @@ -1,140 +0,0 @@ -package object; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -public class HashMapCount extends SuperHashMap -{ - public int incrementCount(K key) - { - return incrementCount(key, 1); - } - public int incrementCount(K key, int amount) - { - Integer currentLong = get(key); - if (currentLong == null) - { - currentLong = Integer.valueOf(0); - } - - int val = currentLong.intValue() + amount; - put(key, Integer.valueOf(val)); - return val; - } - public int getCount(K key) - { - Integer value = get(key); - if (value == null) - { - return 0; - } - - return value.intValue(); - } - public int getTotalCount() - { - HandyArrayList counts = getValuesAsVector(); - int total = 0; - for (int count : counts) - { - total += count; - } - - return total; - } - - public K getKeyWithHighestCount() - { - Map.Entry highest = getHighestEntry(); - return highest.getKey(); - } - public int getHighestCount() - { - Map.Entry highest = getHighestEntry(); - return highest.getValue(); - } - private Map.Entry getHighestEntry() - { - Set> set = entrySet(); - - HandyArrayList> list = new HandyArrayList<>(set); - list.sort((Map.Entry m1, Map.Entry m2) -> Integer.compare(m1.getValue(), m2.getValue())); - - return list.lastElement(); - } - - /** - * These ONLY WORK FOR INTEGER KEYS - */ - public double calculateAverage() - { - double totalValue = 0; - - Iterator> it = entrySet().iterator(); - for (; it.hasNext(); ) - { - Map.Entry entry = it.next(); - totalValue += (Integer)entry.getKey() * entry.getValue(); - } - - double avg = totalValue / getTotalCount(); - double roundedAvg = (double)Math.round(10 * avg) / 10; - - return roundedAvg; - } - public double calculateMedian() - { - ArrayList allKeys = getFlattenedOrderedList((K k1, K k2) -> Integer.compare((int)k1, (int)k2)); - - int n = allKeys.size(); - if (n % 2 == 0) - { - //Even, so we want either side of the middle value and then to take the average of them.# - int bigIx = n/2; - int smallIx = (n/2) - 1; - - double sum = ((int)allKeys.get(bigIx) + (int)allKeys.get(smallIx)); - - return sum/2; - } - else - { - //Odd, so we just want the middle value. It's (n-1)/2 because of stupid index starting at 0 not 1. - int ix = (n-1)/2; - return (int)allKeys.get(ix); - } - } - - /** - * Returns {1, 1, 1, 1, 1, 2, 2} from {1 -> 5, 2 -> 2} - */ - public ArrayList getFlattenedOrderedList(Comparator comparator) - { - ArrayList ret = new ArrayList<>(); - - Set> set = entrySet(); - - HandyArrayList> list = new HandyArrayList<>(set); - - if (comparator != null) - { - list.sort((Map.Entry m1, Map.Entry m2) -> comparator.compare(m1.getKey(), m2.getKey())); - } - - for (int i=0; i entry = list.get(i); - int numberToAdd = entry.getValue(); - K key = entry.getKey(); - for (int j=0; j extends SuperHashMap> -{ - public void putInList(K key, V value) - { - HandyArrayList list = get(key); - if (list == null) - { - list = new HandyArrayList<>(); - put(key, list); - } - - list.add(value); - } - - public int getValuesSize() - { - int totalSize = 0; - HandyArrayList> valueVectors = getValuesAsVector(); - for (HandyArrayList valueVector : valueVectors) - { - totalSize += valueVector.size(); - } - - return totalSize; - - } -} diff --git a/core/src/main/java/object/SuperHashMap.java b/core/src/main/java/object/SuperHashMap.java deleted file mode 100644 index 796cd90..0000000 --- a/core/src/main/java/object/SuperHashMap.java +++ /dev/null @@ -1,66 +0,0 @@ -package object; - -import java.util.HashMap; -import java.util.Iterator; - -/** - * Wrap up a hashmap to introduce extra helper methods - */ -public class SuperHashMap extends HashMap -{ - public HandyArrayList getKeysAsVector() - { - HandyArrayList keys = new HandyArrayList<>(); - - Iterator it = keySet().iterator(); - for (; it.hasNext(); ) - { - K key = it.next(); - keys.add(key); - } - - return keys; - } - - public HandyArrayList getValuesAsVector() - { - return getValuesAsVector(false); - } - public HandyArrayList getValuesAsVector(boolean distinct) - { - HandyArrayList values = new HandyArrayList<>(); - - Iterator it = values().iterator(); - for (; it.hasNext(); ) - { - V val = it.next(); - if (!distinct - || !values.contains(val)) - { - values.add(val); - } - } - - return values; - } - - public SuperHashMap factoryCopy() - { - SuperHashMap ret = new SuperHashMap<>(); - ret.putAll(this); - return ret; - } - - public void removeAllWithValue(V value) - { - HandyArrayList keys = getKeysAsVector(); - for (K key : keys) - { - V val = get(key); - if (val.equals(value)) - { - remove(key); - } - } - } -} diff --git a/core/src/main/java/util/Debug.java b/core/src/main/java/util/Debug.java index 7bb4083..671ff6a 100644 --- a/core/src/main/java/util/Debug.java +++ b/core/src/main/java/util/Debug.java @@ -8,151 +8,109 @@ import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; -public class Debug implements CoreRegistry -{ - private static DebugOutput output = null; - private static boolean logToSystemOut = false; - - private static ThreadFactory loggerFactory = r -> new Thread(r, "Logger"); - private static ExecutorService logService = Executors.newFixedThreadPool(1, loggerFactory); - - - public static void append(String text) - { - append(text, true); - } - public static void append(String text, boolean logging) - { - append(text, logging, true); - } - private static void append(final String text, boolean logging, final boolean includeDate) - { - append(text, logging, includeDate, null); - } - private static void append(final String text, boolean logging, final boolean includeDate, final BooleanWrapper haveStackTraced) - { - if (!logging) - { - return; - } - - Runnable logRunnable = new Runnable() - { - @Override - public void run() - { - String time = ""; - if (includeDate) - { - time = getCurrentTimeForLogging(); - } - - output.append("\n" + time + text); - - if (logToSystemOut) - { - System.out.println(time + text); - } - - if (haveStackTraced != null) - { - haveStackTraced.setValue(true); - } - } - }; - - logService.execute(logRunnable); - } - - public static void appendWithoutDate(String text) - { - append(" " + text, true, false); - } - - public static void appendBanner(String text) - { - appendBanner(text, true); - } - - public static void appendBanner(String text, boolean logging) - { - if (logging) - { - int length = text.length(); - - String starStr = ""; - for (int i=0; i new Thread(r, "Logger"); + private static ExecutorService logService = Executors.newFixedThreadPool(1, loggerFactory); + + + public static void append(String text) { + append(text, true); + } + + public static void append(String text, boolean logging) { + append(text, logging, true); + } + + private static void append(final String text, boolean logging, final boolean includeDate) { + append(text, logging, includeDate, null); + } + + private static void append(final String text, boolean logging, final boolean includeDate, final BooleanWrapper haveStackTraced) { + if (!logging) { + return; + } + + Runnable logRunnable = new Runnable() { + @Override + public void run() { + String time = ""; + if (includeDate) { + time = getCurrentTimeForLogging(); + } + + output.append("\n" + time + text); + + if (haveStackTraced != null) { + haveStackTraced.setValue(true); + } + } + }; + + logService.execute(logRunnable); + } + + public static void appendWithoutDate(String text) { + append(" " + text, true, false); + } + + public static void appendBanner(String text) { + appendBanner(text, true); + } + + public static void appendBanner(String text, boolean logging) { + if (logging) { + int length = text.length(); + + String starStr = ""; + for (int i = 0; i < length + 4; i++) { + starStr += "*"; + } + + append(starStr, true); + append(text, true); + append(starStr, true); + } + } + + /** + * Stack Trace methods + */ + public static void stackTrace(String reason) { + Throwable t = new Throwable(); + stackTrace(t, reason); + } + + public static void stackTrace(Throwable t) { + stackTrace(t, ""); + } + + public static void stackTrace(Throwable t, String message) { + String datetime = getCurrentTimeForLogging(); + + String trace = ""; + if (!message.equals("")) { + trace += datetime + message + "\n"; + } + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + t.printStackTrace(pw); + trace += datetime + sw.toString(); + + BooleanWrapper haveAppendedStackTrace = new BooleanWrapper(false); + append(trace, true, false, haveAppendedStackTrace); + } + + public static String getCurrentTimeForLogging() { + long time = System.currentTimeMillis(); + + SimpleDateFormat sdf = new SimpleDateFormat("dd/MM HH:mm:ss.SSS"); + return sdf.format(time) + " "; + } + + public static void initialise(DebugOutput output) { + Debug.output = output; + } } \ No newline at end of file diff --git a/core/src/main/java/util/FileUtil.java b/core/src/main/java/util/FileUtil.java index 0f1bb79..69e0e39 100644 --- a/core/src/main/java/util/FileUtil.java +++ b/core/src/main/java/util/FileUtil.java @@ -24,43 +24,6 @@ public class FileUtil { - public static String getMd5Crc(String filePath) - { - String crc = null; - - try - { - MessageDigest md = MessageDigest.getInstance("MD5"); - md.update(Files.readAllBytes(Paths.get(filePath))); - byte[] digest = md.digest(); - - crc = EncryptionUtil.base64Interface.encode(digest); - } - catch (Throwable t) - { - Debug.append("Caught " + t + " trying to get CRC of file"); - } - - return crc; - } - - public static long getFileSize(String filePath) - { - long fileSize = -1; - File f = new File(filePath); - - try (FileInputStream fis = new FileInputStream(f)) - { - fileSize = fis.getChannel().size(); - } - catch (Throwable t) - { - Debug.stackTrace(t, "Couldn't obtain file size for path " + filePath); - } - - return fileSize; - } - public static void saveTextToFile(String text, Path destinationPath) { Charset charset = Charset.forName("US-ASCII"); @@ -101,10 +64,4 @@ public static void encodeAndSaveToFile(Path destinationPath, String stringToWrit String encodedStringToWrite = EncryptionUtil.base64Interface.encode(stringToWrite.getBytes()); saveTextToFile(encodedStringToWrite, destinationPath); } - - public static String stripFileExtension(String filename) - { - int ix = filename.indexOf('.'); - return filename.substring(0, ix); - } } diff --git a/core/src/main/java/util/MessageSender.java b/core/src/main/java/util/MessageSender.java index 73c7200..b91b85c 100644 --- a/core/src/main/java/util/MessageSender.java +++ b/core/src/main/java/util/MessageSender.java @@ -8,9 +8,12 @@ import java.net.SocketException; import java.net.SocketTimeoutException; +import kotlin.Pair; import org.w3c.dom.Document; import org.w3c.dom.Element; +import static utils.InjectedThings.logger; + public class MessageSender implements Runnable { private MessageSenderParams messageParms = null; @@ -152,7 +155,7 @@ private String retryOrStackTrace(Throwable t) && messageParms.getAlwaysRetryOnSoTimeout()) { //Always retry, don't bother logging a line - Debug.append("Had SocketTimeoutException for " + messageName + ", retrying", !AbstractClient.devMode); + logger.info("messageFailure", "Had SocketTimeoutException for " + messageName + ", retrying"); return sendMessage(); } @@ -161,16 +164,16 @@ private String retryOrStackTrace(Throwable t) { currentRetries++; messageParms.setMillis(0); - Debug.append(t.getMessage() + " for " + messageName + ", will retry (" + currentRetries + "/" + retries + ")"); + logger.info("messageFailure", t.getMessage() + " for " + messageName + ", will retry (" + currentRetries + "/" + retries + ")"); return sendMessage(); } else { - Debug.append("Failed to send message after " + retries + " retries."); - Debug.append("Message: " + messageParms.getMessageString()); - Debug.stackTraceSilently(t); - Debug.append("Previous stack:"); - Debug.stackTraceSilently(messageParms.getCreationStack()); + logger.error("messageFailure", + "Failed to send message after " + retries + " retries", + t, + new Pair<>("message", messageParms.getMessageString()), + new Pair<>("previousStack", messageParms.getCreationStack())); if (client.isCommunicatingWithServer()) { diff --git a/core/src/main/java/util/MessageUtil.java b/core/src/main/java/util/MessageUtil.java index b036e1a..5102ff7 100644 --- a/core/src/main/java/util/MessageUtil.java +++ b/core/src/main/java/util/MessageUtil.java @@ -15,11 +15,14 @@ import javax.crypto.SecretKey; +import kotlin.Pair; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import utils.InjectedThings; +import static utils.InjectedThings.logger; + public class MessageUtil implements OnlineConstants { // public static final String LIVE_IP = "54.149.26.201"; //Live @@ -171,10 +174,12 @@ public static void stackTraceAndDumpMessages(Throwable t, Throwable clientStackT String encryptedResponseStr) { AbstractClient.getInstance().finishServerCommunication(); - - Debug.stackTraceSilently(t); - Debug.stackTrace(clientStackTrace, "Previous stack:"); - Debug.append("messageString was: " + messageStr); + + logger.error("messageFailure", + "Failed to send message", + t, + new Pair<>("message", messageStr), + new Pair<>("previousStack", clientStackTrace)); String responseStr = null; if (symmetricKey != null) diff --git a/core/src/main/java/util/XmlUtil.java b/core/src/main/java/util/XmlUtil.java index 0bf1f6f..d2f0ecb 100644 --- a/core/src/main/java/util/XmlUtil.java +++ b/core/src/main/java/util/XmlUtil.java @@ -4,8 +4,6 @@ import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; -import java.util.Iterator; -import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -20,8 +18,6 @@ import org.w3c.dom.NodeList; import org.xml.sax.InputSource; -import object.SuperHashMap; - public class XmlUtil { //Cache one instance of the factories - I've seen from thread stacks that constructing it each time is really slow @@ -150,17 +146,6 @@ public static long getAttributeLong(Element rootElement, String attributeName) return Long.parseLong(attribute); } - public static double getAttributeDouble(Element rootElement, String attributeName) - { - String attribute = rootElement.getAttribute(attributeName); - if (attribute.equals("")) - { - return 0; - } - - return Double.parseDouble(attribute); - } - public static boolean getAttributeBoolean(Element rootElement, String attributeName) { String value = rootElement.getAttribute(attributeName); @@ -226,38 +211,4 @@ public static Document factorySimpleMessage(String username, String rootName) message.appendChild(rootElement); return message; } - - public static SuperHashMap readIntegerHashMap(Element rootElement, String tagName, String keyTag, String valueTag) - { - SuperHashMap hm = new SuperHashMap<>(); - - NodeList children = rootElement.getElementsByTagName(tagName); - int size = children.getLength(); - for (int i=0; i it = hm.entrySet().iterator(); - for (; it.hasNext(); ) - { - Map.Entry entry = it.next(); - - Element child = xmlDoc.createElement(tagName); - child.setAttribute(keyTag, "" + entry.getKey()); - child.setAttribute(valueTag, "" + entry.getValue()); - rootElement.appendChild(child); - } - } } diff --git a/server/src/main/java/server/EntropyServer.java b/server/src/main/java/server/EntropyServer.java index 00f2682..a0a65b8 100644 --- a/server/src/main/java/server/EntropyServer.java +++ b/server/src/main/java/server/EntropyServer.java @@ -38,1551 +38,1313 @@ import static utils.ThreadUtilKt.dumpThreadStacks; public final class EntropyServer extends JFrame - implements ActionListener, - KeyListener, - OnlineConstants, - ServerCommands -{ - //Statics - private static final int CORE_POOL_SIZE = 50; - private static final int MAX_POOL_SIZE = 500; - private static final int MAX_QUEUE_SIZE = 100; - private static final int KEEP_ALIVE_TIME = 20; - - //Files - private static final Path FILE_PATH_USED_KEYS = Paths.get("C:\\EntropyServer\\UsedKeys.txt"); - private static final Path FILE_PATH_CLIENT_VERSION = Paths.get("C:\\EntropyServer\\Version.txt"); - - //Console - private static DebugConsole console = new DebugConsole(); - - //Caches - private ExtendedConcurrentHashMap hmUserConnectionByIpAndPort = new ExtendedConcurrentHashMap<>(); - private ConcurrentHashMap hmRoomByName = new ConcurrentHashMap<>(); - private ArrayList lobbyMessages = new ArrayList<>(); - private ArrayList usedSymmetricKeys = new ArrayList<>(); - - //Stats stuff - private ConcurrentHashMap hmFunctionsReceivedByMessageType = new ConcurrentHashMap<>(); - private ConcurrentHashMap hmFunctionsHandledByMessageType = new ConcurrentHashMap<>(); - private ConcurrentHashMap hmNotificationsSentByNotificationType = new ConcurrentHashMap<>(); - private ConcurrentHashMap hmNotificationsAttemptedByNotificationType = new ConcurrentHashMap<>(); - private AtomicInteger functionsReceived = new AtomicInteger(0); - private AtomicInteger functionsHandled = new AtomicInteger(0); - private AtomicInteger notificationsAttempted = new AtomicInteger(0); - private AtomicInteger notificationsSent = new AtomicInteger(0); - private AtomicInteger totalFunctionsHandled = new AtomicInteger(0); - private AtomicInteger totalNotificationsSent = new AtomicInteger(0); - private AtomicInteger mostFunctionsReceived = new AtomicInteger(0); - private AtomicInteger mostFunctionsHandled = new AtomicInteger(0); - - //Blacklist - private ConcurrentHashMap hmMessagesReceivedByIp = new ConcurrentHashMap<>(); - private ConcurrentHashMap blacklist = new ConcurrentHashMap<>(); - private boolean usingBlacklist = true; - private int blacklistMinutes = 15; - private int messagesPerSecondThreshold = 15; - - //Tracing - private ArrayList tracedMessages = new ArrayList<>(); - private ArrayList tracedUsers = new ArrayList<>(); - private boolean traceAll = false; - - //Properties - private static boolean devMode = false; - private boolean online = false; - private boolean notificationSocketLogging = false; - - //Seed - private static long currentSeedLong = 4613352884640512L; - - //Thread Pool - private BlockingQueue blockQueue = new ArrayBlockingQueue<>(MAX_QUEUE_SIZE); - private EntropyThreadPoolExecutor tpe = new EntropyThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, - KEEP_ALIVE_TIME, TimeUnit.SECONDS, blockQueue, this); - - //Other - private String lastCommand = ""; - - private PrivateKey privateKey = null; - - public EntropyServer() - { - try - { - setTitle("Entropy Server"); - getContentPane().setLayout(new BorderLayout(0, 0)); - getContentPane().add(panel, BorderLayout.NORTH); - panel.add(commandLine); - commandLine.setColumns(13); - panel.add(btnKill); - panel.add(btnThreads); - panel.add(lblFunctionsReceived); - lblFunctionsReceived.setHorizontalAlignment(SwingConstants.CENTER); - lblFunctionsReceived.setPreferredSize(new Dimension(40, 20)); - panel.add(lblFunctionsHandled); - lblFunctionsHandled.setHorizontalAlignment(SwingConstants.CENTER); - lblFunctionsHandled.setPreferredSize(new Dimension(40, 20)); - getContentPane().add(panel_1, BorderLayout.CENTER); - - panel_1.add(btnMemory); - panel_1.add(btnConsole); - panel_1.add(btnSendLogs); - tglbtnScrollLock.setPreferredSize(new Dimension(26, 26)); - tglbtnScrollLock.setIcon(new ImageIcon(EntropyServer.class.getResource("/buttons/key.png"))); - tglbtnScrollLock.setSelectedIcon(new ImageIcon(EntropyServer.class.getResource("/buttons/keySelected.png"))); - panel_1.add(tglbtnScrollLock); - lblNotificationsAttempted.setPreferredSize(new Dimension(40, 20)); - lblNotificationsAttempted.setHorizontalAlignment(SwingConstants.CENTER); - - panel_1.add(lblNotificationsAttempted); - lblNotificationsSent.setPreferredSize(new Dimension(40, 20)); - lblNotificationsSent.setHorizontalAlignment(SwingConstants.CENTER); - - panel_1.add(lblNotificationsSent); - - tglbtnScrollLock.addActionListener(this); - btnThreads.addActionListener(this); - btnKill.addActionListener(this); - commandLine.addActionListener(this); - btnConsole.addActionListener(this); - btnSendLogs.addActionListener(this); - btnMemory.addActionListener(this); - - commandLine.addKeyListener(this); - } - catch (Throwable t) - { - Debug.stackTrace(t); - } - } - - private final JButton btnKill = new JButton("Kill"); - private final JButton btnThreads = new JButton("Threads"); - private final JTextField commandLine = new JTextField(); - private final JLabel lblFunctionsHandled = new JLabel(""); - private final JLabel lblFunctionsReceived = new JLabel(""); - private final JPanel panel = new JPanel(); - private final JPanel panel_1 = new JPanel(); - private final JButton btnConsole = new JButton("Console"); - private final JButton btnSendLogs = new JButton("Send Logs"); - private final JToggleButton tglbtnScrollLock = new JToggleButton(""); - private final JButton btnMemory = new JButton("Memory"); - private final JLabel lblNotificationsAttempted = new JLabel(""); - private final JLabel lblNotificationsSent = new JLabel(""); - - public static void main(String args[]) - { - EntropyServer server = new EntropyServer(); - Thread.setDefaultUncaughtExceptionHandler(new LoggerUncaughtExceptionHandler()); - - //Initialise interfaces etc - EncryptionUtil.setBase64Interface(new Base64Desktop()); - Debug.initialise(console); - - //Set other variables on Debug - Debug.setLogToSystemOut(devMode); - - int length = args.length; - for (int i=0; i messagesPerSecondThreshold) - { - addToBlacklist(ip, ">" + messagesPerSecondThreshold + " msg/s"); - } - - } - private void clearIpMessageCounts() - { - hmMessagesReceivedByIp = new ConcurrentHashMap<>(); - } - - public void incrementFunctionsReceived() - { - functionsReceived.addAndGet(1); - } - public void incrementFunctionsHandled() - { - functionsHandled.addAndGet(1); - totalFunctionsHandled.addAndGet(1); - } - public void incrementFunctionsReceivedAndHandledForMessage(String message) - { - incrementFunctionsReceivedForMessage(message); - incrementFunctionsHandledForMessage(message); - } - public void incrementFunctionsReceivedForMessage(String message) - { - incrementStatForMessage(message, hmFunctionsReceivedByMessageType); - } - public void incrementFunctionsHandledForMessage(String message) - { - incrementStatForMessage(message, hmFunctionsHandledByMessageType); - } - public void incrementNotificationsSentForMessage(String message) - { - incrementStatForMessage(message, hmNotificationsSentByNotificationType); - notificationsSent.addAndGet(1); - totalNotificationsSent.addAndGet(1); - } - public void incrementNotificationsAttemptedForMessage(String message) - { - incrementStatForMessage(message, hmNotificationsAttemptedByNotificationType); - notificationsAttempted.addAndGet(1); - } - private void incrementStatForMessage(String message, ConcurrentHashMap statsHm) - { - AtomicInteger functions = statsHm.get(message); - if (functions == null) - { - functions = new AtomicInteger(0); - } - - functions.incrementAndGet(); - statsHm.put(message, functions); - } - - private void clearFunctionStats() - { - int functionsReceivedInt = functionsReceived.intValue(); - int functionsHandledInt = functionsHandled.intValue(); - int notificationsAttemptedInt = notificationsAttempted.intValue(); - int notificationsSentInt = notificationsSent.intValue(); - - if (functionsReceivedInt > mostFunctionsReceived.intValue()) - { - mostFunctionsReceived.set(functionsReceivedInt); - } - - if (functionsHandledInt > mostFunctionsHandled.intValue()) - { - mostFunctionsHandled.set(functionsHandledInt); - } - - int newFunctionsReceived = functionsReceivedInt - functionsHandledInt; - functionsReceived.set(newFunctionsReceived); - functionsHandled.set(0); - - int newNotificationsAttempted = notificationsAttemptedInt - notificationsSentInt; - notificationsAttempted.set(newNotificationsAttempted); - notificationsSent.set(0); - - clearIpMessageCounts(); - } - - public boolean isAlreadyOnline(String username) - { - if (username.equalsIgnoreCase("Admin")) - { - return true; - } - - return getUserConnectionForUsername(username) != null; - } - - public ArrayList getUserConnectionsForUsernames(HashSet usernames) - { - ArrayList uscs = new ArrayList<>(); - - Iterator it = usernames.iterator(); - for (; it.hasNext(); ) - { - String username = it.next(); - UserConnection usc = getUserConnectionForUsername(username); - if (usc != null) - { - uscs.add(usc); - } - } - - return uscs; - } - - public UserConnection getUserConnectionForUsername(String username) - { - Iterator it = hmUserConnectionByIpAndPort.keySet().iterator(); - for (; it.hasNext();) - { - String ip = it.next(); - UserConnection usc = hmUserConnectionByIpAndPort.get(ip); - String usernameOnUsc = usc.getUsername(); - if (usernameOnUsc != null - && usernameOnUsc.equals(username)) - { - return usc; - } - } - - return null; - } - - public void updateMostConcurrentUsers() - { - StatisticsUtil.updateMostConcurrentUsers(getCurrentUserCount()); - } - - private int getCurrentUserCount() - { - int count = 0; - - Iterator it = hmUserConnectionByIpAndPort.keySet().iterator(); - for (; it.hasNext();) - { - String ip = it.next(); - UserConnection usc = hmUserConnectionByIpAndPort.get(ip); - String usernameOnUsc = usc.getUsername(); - if (usernameOnUsc != null) - { - count++; - } - } - - return count; - } - - public void removeFromUsersOnline(UserConnection usc) - { - //Null these out so we don't try to send any more notifications - usc.destroyNotificationSockets(); - - //Cache this key as used so it can't be re-used by someone else in a replay attack - SecretKey symmetricKey = usc.getSymmetricKey(); - cacheKeyAsUsed(symmetricKey); - - //Need to remove them from rooms too - String username = usc.getUsername(); - if (username != null) - { - ColourGenerator.freeUpColour(usc.getColour()); - - List rooms = getRooms(); - for (int i=0; i rooms = getRooms(); - int size = rooms.size(); - for (int i=0; i 0) - { - Debug.append("Removed " + countRemoved + " excess rooms"); - } - - Debug.append("Cleared lobby messages"); - lobbyMessages.clear(); - } - - public void addToChatHistory(String id, String message, String colour, String username) - { - OnlineMessage messageObj = new OnlineMessage(colour, message, username); - addToChatHistory(id, messageObj); - } - public void addAdminMessage(String message) - { - OnlineMessage messageObj = new OnlineMessage("black", message, "Admin"); - Iterator it = hmRoomByName.keySet().iterator(); - for (; it.hasNext();) - { - String name = it.next(); - addToChatHistory(name, messageObj); - } - - addToChatHistory(LOBBY_ID, messageObj); - } - private void addToChatHistory(String name, OnlineMessage message) - { - if (name.equals(LOBBY_ID)) - { - lobbyMessages.add(message); - ArrayList usersToNotify = getUserConnections(true); - - Document chatMessage = XmlBuilderServer.getChatNotification(name, message); - sendViaNotificationSocket(usersToNotify, chatMessage, XmlConstants.SOCKET_NAME_CHAT); - } - else - { - Room room = hmRoomByName.get(name); - room.addToChatHistoryAndNotifyUsers(message); - } - } - - public void lobbyChanged() - { - lobbyChanged(null); - } - public void lobbyChanged(UserConnection userToExclude) - { - ArrayList usersToNotify = getUserConnections(true); - if (userToExclude != null) - { - usersToNotify.remove(userToExclude); - } - - Document lobbyMessage = XmlUtil.factoryNewDocument(); - XmlBuilderServer.appendLobbyResponse(lobbyMessage, this); - - sendViaNotificationSocket(usersToNotify, lobbyMessage, XmlConstants.SOCKET_NAME_LOBBY); - } - - private void sendViaNotificationSocket(ArrayList uscs, Document message, String socketName) - { - sendViaNotificationSocket(uscs, message, socketName, false); - } - public void sendViaNotificationSocket(ArrayList uscs, Document message, String socketName, boolean blocking) - { - AtomicInteger counter = null; - if (blocking) - { - int size = uscs.size(); - counter = new AtomicInteger(size); - } - - sendViaNotificationSocket(uscs, message, socketName, counter); - } - - private void sendViaNotificationSocket(ArrayList uscs, Document message, String socketName, AtomicInteger counter) - { - int size = uscs.size(); - for (int i=0; i 0) - { - //So you wait. You waiiiit. Yoouuu waiiiiiiit. - try {Thread.sleep(500);} catch (Throwable t) {} - } - } - } - - public List getChatHistory(String id) - { - if (id.equals(LOBBY_ID)) - { - return lobbyMessages; - } - - Room room = hmRoomByName.get(id); - return room.getChatHistory(); - } - - public PrivateKey getPrivateKey() - { - return privateKey; - } - - private void registerNewRoom(String roomName, int mode, int players, int jokerQuantity, int jokerValue) - { - registerNewRoom(roomName, mode, players, jokerQuantity, jokerValue, false, false, false, false, false); - } - private Room registerNewRoom(String roomName, int mode, int players, int jokerQuantity, int jokerValue, - boolean includeMoons, boolean includeStars, boolean illegalAllowed, boolean negativeJacks, - boolean cardReveal) - { - Iterator it = hmRoomByName.keySet().iterator(); - for (; it.hasNext(); ) - { - String id = it.next(); - Room room = hmRoomByName.get(id); - - String nameToCheck = room.getRoomName(); - if (nameToCheck.equals(roomName)) - { - Debug.append("Not creating room " + nameToCheck + " as a room with that name already exists."); - return null; - } - } - - Room room = new Room(roomName, mode, players, this); - room.setJokerQuantity(jokerQuantity); - room.setJokerValue(jokerValue); - room.setIncludeMoons(includeMoons); - room.setIncludeStars(includeStars); - room.setIllegalAllowed(illegalAllowed); - room.setNegativeJacks(negativeJacks); - room.setCardReveal(cardReveal); - room.initialiseGame(); - hmRoomByName.put(roomName, room); - - Debug.append("Room created: " + roomName); - return room; - } - public void registerCopy(Room room) - { - String roomName = room.getRoomName(); - int mode = room.getMode(); - int players = room.getPlayers(); - int jokerQuantity = room.getJokerQuantity(); - int jokerValue = room.getJokerValue(); - boolean includeMoons = room.getIncludeMoons(); - boolean includeStars = room.getIncludeStars(); - boolean illegalAllowed = room.getIllegalAllowed(); - boolean negativeJacks = room.getNegativeJacks(); - boolean cardReveal = room.getCardReveal(); - - int index = roomName.indexOf(' ') + 1; - String roomNumberStr = roomName.substring(index); - int roomNumber = Integer.parseInt(roomNumberStr); - - String newRoomName = roomName.substring(0, index) + (roomNumber+1); - - Room newRoom = registerNewRoom(newRoomName, mode, players, jokerQuantity, jokerValue, - includeMoons, includeStars, illegalAllowed, negativeJacks, cardReveal); - - if (newRoom != null) - { - newRoom.setIsCopy(true); - lobbyChanged(); - } - } - public void removeRoom(String roomName) - { - hmRoomByName.remove(roomName); - lobbyChanged(); - } - - public ArrayList getRooms() - { - ArrayList list = new ArrayList<>(); - Iterator it = hmRoomByName.keySet().iterator(); - - for (; it.hasNext(); ) - { - String id = it.next(); - Room room = hmRoomByName.get(id); - list.add(room); - } - - return list; - } - - public Room getRoomForName(String name) - { - return hmRoomByName.get(name); - } - public void toggleOnline() - { - online = !online; - } - public boolean isOnline() - { - return online; - } - - /** - * Generate a secure seed based on: - * - Current time in nanos - * - A private seed known to the Server which is incremented on each hit - * - The total number of notifications that have been sent (generate a random long from this) - */ - public long generateSeed() - { - currentSeedLong++; - - Random rand = new Random(totalNotificationsSent.get()); - return System.nanoTime() + currentSeedLong + rand.nextLong(); - } - - public ArrayList getUserConnections(boolean onlyLoggedIn) - { - ArrayList uscs = new ArrayList<>(); - - Iterator it = hmUserConnectionByIpAndPort.keySet().iterator(); - for (; it.hasNext(); ) - { - String ip = it.next(); - UserConnection usc = hmUserConnectionByIpAndPort.get(ip); - if (onlyLoggedIn - && usc.getUsername() == null) - { - continue; - } - - uscs.add(usc); - } - - return uscs; - } - public UserConnection getUserConnectionForIpAndPort(String ipAndPort) - { - return hmUserConnectionByIpAndPort.get(ipAndPort); - } - public void setUserConnectionForIpAndPort(String ipAndPort, UserConnection usc) - { - hmUserConnectionByIpAndPort.put(ipAndPort, usc); - } - public ArrayList getAllPortsCurrentlyUsedByIp(String ipAddress) - { - Iterator it = hmUserConnectionByIpAndPort.keySet().iterator(); - ArrayList list = new ArrayList<>(); - - for (; it.hasNext();) - { - String ipAndPort = it.next(); - int index = ipAndPort.indexOf("_"); - String ipToCheck = ipAndPort.substring(0, index); - if (ipToCheck.equals(ipAddress)) - { - String port = ipAndPort.substring(index+1); - list.add(port); - } - } - - return list; - } - - public boolean getDevMode() - { - return devMode; - } - - public boolean getNotificationSocketLogging() - { - return notificationSocketLogging; - } - - @Override - public void actionPerformed(ActionEvent arg0) - { - try - { - Component source = (Component)arg0.getSource(); - if (source == btnKill) - { - onStop(); - - System.exit(0); - } - else if (source == btnThreads) - { - processCommand(COMMAND_DUMP_THREADS); - } - else if (source == btnMemory) - { - processCommand("memory"); - } - else if (source == commandLine) - { - String command = commandLine.getText(); - commandLine.setText(""); - processCommand(command); - } - else if (source == btnConsole) - { - if (!console.isVisible()) - { - console.setTitle("Console"); - console.setSize(1000, 600); - console.setLocationRelativeTo(null); - console.setVisible(true); - } - else - { - console.toFront(); - } - } - else if (source == tglbtnScrollLock) - { - boolean scrollLock = tglbtnScrollLock.isSelected(); - console.setScrollLock(scrollLock); - } - } - catch (Throwable t) - { - Debug.stackTrace(t); - } - } - - public void cacheKeyAsUsed(SecretKey symmetricKey) - { - usedSymmetricKeys.add(symmetricKey); - } - public boolean keyHasAlreadyBeenUsed(SecretKey symmetricKey) - { - return usedSymmetricKeys.contains(symmetricKey); - } - private void writeUsedKeysToFile() - { - Charset charset = Charset.forName("US-ASCII"); - try (BufferedWriter writer = Files.newBufferedWriter(FILE_PATH_USED_KEYS, charset)) - { - for (SecretKey key : usedSymmetricKeys) - { - String word = EncryptionUtil.convertSecretKeyToString(key); - writer.write(word); - writer.newLine(); - } - - //Write out current ones as well - Iterator it = hmUserConnectionByIpAndPort.keySet().iterator(); - for (; it.hasNext(); ) - { - String ip = it.next(); - UserConnection usc = hmUserConnectionByIpAndPort.get(ip); - - SecretKey symmetricKey = usc.getSymmetricKey(); - if (symmetricKey != null) - { - String word = EncryptionUtil.convertSecretKeyToString(symmetricKey); - writer.write(word); - writer.newLine(); - } - } - } - catch (IOException x) - { - Debug.stackTrace(x); - } - } - private void readUsedKeysFromFile() - { - try - { - Charset charset = Charset.forName("US-ASCII"); - List usedKeys = Files.readAllLines(FILE_PATH_USED_KEYS, charset); - - int keys = 0; - for (String key : usedKeys) - { - SecretKey secretKey = EncryptionUtil.reconstructKeyFromString(key); - if (secretKey != null) - { - keys++; - usedSymmetricKeys.add(secretKey); - } - } - - Debug.append("Read in " + keys + " used keys"); - } - catch (Throwable t) - { - Debug.append("Caught " + t + " trying to read in used keys"); - } - } - - public ConcurrentHashMap getBlacklist() - { - return blacklist; - } - - public void addToBlacklist(String ipAndPort, String reason) - { - ArrayList tokens = StringUtil.getListFromDelims(ipAndPort, "_"); - String ip = tokens.get(0); - - if (usingBlacklist) - { - BlacklistEntry entry = new BlacklistEntry(reason); - blacklist.put(ip, entry); - - Debug.append("Blacklisted " + ip + ". Reason: " + reason); - } - else - { - Debug.append("Not blacklisting " + ip + " because this is turned off. Reason: " + reason); - } - } - - public boolean isBlacklisted(String ipAddress) - { - if (!usingBlacklist) - { - return false; - } - - BlacklistEntry entry = blacklist.get(ipAddress); - return entry != null; - } - - public boolean messageIsTraced(String nodeName, String username) - { - if (traceAll) - { - return true; - } - - if (tracedMessages.contains(nodeName)) - { - return true; - } - - if (tracedUsers.contains(username)) - { - return true; - } - - return false; - } - - private void toggleServerOnline() - { - ToggleAvailabilityRunnable runnable = new ToggleAvailabilityRunnable(this); - executeInWorkerPool(runnable); - } - - private void processCommand(String command) - { - Debug.append("[Command entered: " + command + "]"); - if (command.equals("help")) - { - printCommands(); - } - else if (command.equals(COMMAND_DUMP_THREADS)) - { - dumpServerThreads(); - } - else if (command.equals(COMMAND_DUMP_THREAD_STACKS)) - { - dumpThreadStacks(); - } - else if (command.equals(COMMAND_DUMP_USERS)) - { - Debug.append(getCurrentUserCount() + " user(s) online:"); - Iterator it = hmUserConnectionByIpAndPort.keySet().iterator(); - for (; it.hasNext();) - { - String ip = it.next(); - UserConnection usc = hmUserConnectionByIpAndPort.get(ip); - Debug.appendWithoutDate("" + usc); - } - } - else if (command.equals(COMMAND_TRACE_ALL)) - { - traceAll = !traceAll; - Debug.append("Tracing all messages: " + traceAll); - } - else if (command.startsWith(COMMAND_TRACE_USER)) - { - String username = command.substring(COMMAND_TRACE_USER.length()); - if (tracedUsers.contains(username)) - { - Debug.append("Stopped tracing user " + username); - tracedUsers.remove(username); - } - else - { - Debug.append("Tracing user " + username); - tracedUsers.add(username); - } - } - else if (command.startsWith(COMMAND_TRACE_MESSAGE_AND_RESPONSE)) - { - String messageStr = command.substring(COMMAND_TRACE_MESSAGE_AND_RESPONSE.length()); - - if (tracedMessages.contains(messageStr)) - { - Debug.append("Stopped tracing " + messageStr + " and its responses"); - tracedMessages.remove(messageStr); - } - else - { - Debug.append("Tracing message " + messageStr + " and its responses"); - tracedMessages.add(messageStr); - } - } - else if (command.equals(COMMAND_SHUT_DOWN)) - { - toggleServerOnline(); - } - else if (command.startsWith(COMMAND_RESET)) - { - String roomIdStr = command.substring(COMMAND_RESET.length()); - Room room = hmRoomByName.get(roomIdStr); - if (room == null) - { - Debug.append("No room found for ID " + roomIdStr); - return; - } - - Debug.append("Resetting currentPlayers for room " + roomIdStr); - room.resetCurrentPlayers(); - } - else if (command.equals(COMMAND_CLEAR_ROOMS)) - { - if (!hmUserConnectionByIpAndPort.isEmpty()) - { - Debug.append("Not clearing down rooms as there are active user connections."); - return; - } - - resetLobby(); - } - else if (command.equals(COMMAND_DUMP_STATS)) - { - Debug.appendBanner("STATS DUMP"); - Debug.appendWithoutDate("Most messages received per second: " + mostFunctionsReceived.get()); - Debug.appendWithoutDate("Most messages handled per second: " + mostFunctionsHandled.get()); - Debug.appendWithoutDate("Total Messages Handled: " + totalFunctionsHandled.get()); - Debug.appendWithoutDate("Total Notifications Sent: " + totalNotificationsSent.get()); - Debug.appendWithoutDate("Most concurrent users: " + StatisticsUtil.getMostConcurrentUsers()); - StatisticsUtil.doGlobalStatsDump(); - Debug.appendWithoutDate(""); - } - else if (command.equals(COMMAND_MESSAGE_STATS)) - { - dumpMessageStats(); - } - else if (command.equals(COMMAND_DECRYPTION_LOGGING)) - { - EncryptionUtil.failedDecryptionLogging = !EncryptionUtil.failedDecryptionLogging; - Debug.appendWithoutDate("Decryption logging: " + EncryptionUtil.failedDecryptionLogging); - } - else if (command.equals(COMMAND_CLEAR_STATS)) - { - StatisticsUtil.clearServerData(); - - totalFunctionsHandled.set(0); - mostFunctionsHandled.set(0); - mostFunctionsReceived.set(0); - } - else if (command.startsWith(COMMAND_SET_CORE_POOL_SIZE)) - { - int newCoreSize = parseArgumentAsInt(command, COMMAND_SET_CORE_POOL_SIZE); - if (newCoreSize > -1) - { - tpe.setCorePoolSize(newCoreSize); - Debug.append("Core pool size is now " + newCoreSize); - } - } - else if (command.startsWith(COMMAND_SET_MAX_POOL_SIZE)) - { - int newMaxSize = parseArgumentAsInt(command, COMMAND_SET_MAX_POOL_SIZE); - if (newMaxSize > -1) - { - tpe.setMaximumPoolSize(newMaxSize); - Debug.append("Max pool size is now " + newMaxSize); - } - } - else if (command.startsWith(COMMAND_SET_KEEP_ALIVE_TIME)) - { - String newKeepAliveTime = command.substring(COMMAND_SET_KEEP_ALIVE_TIME.length()); - tpe.setKeepAliveTime(Integer.parseInt(newKeepAliveTime), TimeUnit.SECONDS); - Debug.append("Keep Alive Time is now " + newKeepAliveTime + "s"); - } - else if (command.equals(COMMAND_POOL_STATS)) - { - Debug.appendWithoutDate("-----------------------------------------"); - Debug.appendWithoutDate("Max size: " + tpe.getMaximumPoolSize()); - Debug.appendWithoutDate("Core size: " + tpe.getCorePoolSize()); - Debug.appendWithoutDate("Alive time: " + tpe.getKeepAliveTime(TimeUnit.SECONDS)); - Debug.appendWithoutDate("-----------------------------------------"); - Debug.appendWithoutDate("Current queue size: " + blockQueue.size()); - Debug.appendWithoutDate("Remaining queue capacity: " + blockQueue.remainingCapacity()); - Debug.appendWithoutDate("Active threads: " + tpe.getActiveCount()); - Debug.appendWithoutDate("Pool size: " + tpe.getPoolSize()); - Debug.appendWithoutDate("Largest pool size: " + tpe.getLargestPoolSize()); - Debug.appendWithoutDate("Completion status: " + tpe.getCompletedTaskCount() + " / " + tpe.getTaskCount()); - Debug.appendWithoutDate("-----------------------------------------"); - } - else if (command.startsWith(COMMAND_FAKE_USERS)) - { - if (!devMode) - { - Debug.append("Not running command as not running in DEV mode"); - return; - } - - int users = parseArgumentAsInt(command, COMMAND_FAKE_USERS); - for (int i=0; i -1) - { - blacklistMinutes = minutes; - usingBlacklist = true; - Debug.appendBanner("Blacklist is ON and set to " + minutes + " minutes"); - } - } - else if (command.startsWith(COMMAND_BLACKLIST)) - { - String ip = command.substring(COMMAND_BLACKLIST.length()); - addToBlacklist(ip, "Manual"); - } - else if (command.startsWith(COMMAND_SET_BLACKLIST_THRESHOLD)) - { - int threshold = parseArgumentAsInt(command, COMMAND_SET_BLACKLIST_THRESHOLD); - if (threshold > -1) - { - messagesPerSecondThreshold = threshold; - Debug.append("Will now blacklist anyone who sends more than " + threshold + " message/s"); - } - } - else if (command.equals(COMMAND_USED_KEYS)) - { - for (SecretKey key : usedSymmetricKeys) - { - String keyStr = EncryptionUtil.convertSecretKeyToString(key); - Debug.appendWithoutDate(keyStr); - } - } - else if (command.equals(COMMAND_DUMP_HASH_MAPS)) - { - dumpHashMaps(); - } - else if (command.startsWith(COMMAND_MEMORY)) - { - boolean forceGc = false; - - int index = command.indexOf(" "); - if (index > -1) - { - String gcBool = command.substring((COMMAND_MEMORY + " ").length()); - forceGc = gcBool.equals("true"); - } - - dumpMemory(forceGc); - } - else if (command.startsWith(COMMAND_NOTIFICATION_LOGGING)) - { - notificationSocketLogging = !notificationSocketLogging; - Debug.append("Notification logging: " + notificationSocketLogging); - } - else if (command.startsWith(COMMAND_NOTIFY_USER)) - { - String username = command.substring(COMMAND_NOTIFY_USER.length()); - UserConnection usc = getUserConnectionForUsername(username); - if (usc == null) - { - Debug.append("Failed to find usc for " + username); - return; - } - - usc.notifySockets(); - } - else if (command.equals(COMMAND_SERVER_VERSION)) - { - Debug.append("Server version: " + OnlineConstants.SERVER_VERSION); - } - else - { - Debug.append("Unrecognised command - type 'help' for a list of available commands"); - return; - } - - lastCommand = command; - } - - private int parseArgumentAsInt(String fullCommand, String prefix) - { - int ret = -1; - String integerStr = fullCommand.substring(prefix.length()); - try - { - ret = Integer.parseInt(integerStr); - } - catch (NumberFormatException nfe) - { - Debug.append("Failed to parse " + integerStr + " as int"); - } - - return ret; - } - - private void printCommands() - { - Debug.append("The available commands are:"); - Debug.appendWithoutDate(COMMAND_DUMP_THREADS); - Debug.appendWithoutDate(COMMAND_DUMP_THREAD_STACKS); - Debug.appendWithoutDate(COMMAND_DUMP_USERS); - Debug.appendWithoutDate(COMMAND_TRACE_ALL); - Debug.appendWithoutDate(COMMAND_TRACE_USEFUL); - Debug.appendWithoutDate(COMMAND_TRACE_USER + ""); - Debug.appendWithoutDate(COMMAND_TRACE_MESSAGE_AND_RESPONSE + ""); - Debug.appendWithoutDate(COMMAND_SHUT_DOWN); - Debug.appendWithoutDate(COMMAND_RESET + ""); - Debug.appendWithoutDate(COMMAND_CLEAR_ROOMS); - Debug.appendWithoutDate(COMMAND_DUMP_STATS); - Debug.appendWithoutDate(COMMAND_MESSAGE_STATS); - Debug.appendWithoutDate(COMMAND_DECRYPTION_LOGGING); - Debug.appendWithoutDate(COMMAND_CLEAR_STATS); - Debug.appendWithoutDate(COMMAND_LAUNCH_DAY); - Debug.appendWithoutDate(COMMAND_POOL_STATS); - Debug.appendWithoutDate(COMMAND_SET_CORE_POOL_SIZE + ""); - Debug.appendWithoutDate(COMMAND_SET_MAX_POOL_SIZE + ""); - Debug.appendWithoutDate(COMMAND_SET_KEEP_ALIVE_TIME + ""); - Debug.appendWithoutDate(COMMAND_DUMP_BLACKLIST); - Debug.appendWithoutDate(COMMAND_BLACKLIST_TIME + ""); - Debug.appendWithoutDate(COMMAND_BLACKLIST_FULL); - Debug.appendWithoutDate(COMMAND_BLACKLIST_OFF); - Debug.appendWithoutDate(COMMAND_BLACKLIST + ""); - Debug.appendWithoutDate(COMMAND_SET_BLACKLIST_THRESHOLD + ""); - Debug.appendWithoutDate(COMMAND_USED_KEYS); - Debug.appendWithoutDate(COMMAND_DUMP_HASH_MAPS); - Debug.appendWithoutDate(COMMAND_MEMORY + ""); - Debug.appendWithoutDate(COMMAND_NOTIFICATION_LOGGING); - Debug.appendWithoutDate(COMMAND_NOTIFY_USER + ""); - Debug.appendWithoutDate(COMMAND_SERVER_VERSION); - } - - private void dumpMessageStats() - { - dumpMessageStats("Message Received Handled", hmFunctionsReceivedByMessageType, hmFunctionsHandledByMessageType); - dumpMessageStats("Notification Attempted Sent", hmNotificationsAttemptedByNotificationType, hmNotificationsSentByNotificationType); - } - private void dumpMessageStats(String titles, ConcurrentHashMap hmReceived, - ConcurrentHashMap hmHandled) - { - if (hmReceived.isEmpty()) - { - return; - } - - Debug.appendWithoutDate("*********************************************************"); - Debug.appendWithoutDate(titles); - Debug.appendWithoutDate("*********************************************************"); - - int totalReceived = 0; - int totalHandled = 0; - - Iterator it = hmReceived.keySet().iterator(); - for (; it.hasNext();) - { - String message = it.next(); - AtomicInteger functionsReceived = hmReceived.get(message); - AtomicInteger functionsHandled = hmHandled.get(message); - if (functionsHandled == null) - { - functionsHandled = new AtomicInteger(0); - } - - totalReceived += functionsReceived.get(); - totalHandled += functionsHandled.get(); - - if (message.length() < 10 - || message.equals("ClientMail")) - { - message += " "; - } - - String row = message + " " + functionsReceived + " " + functionsHandled; - Debug.appendWithoutDate(row); - } - - Debug.appendWithoutDate("*********************************************************"); - Debug.appendWithoutDate("Total " + totalReceived + " " + totalHandled); - Debug.appendWithoutDate("*********************************************************"); - - Debug.appendWithoutDate(""); - } - - private void dumpBlacklist() - { - if (blacklist.isEmpty()) - { - return; - } - - Debug.appendWithoutDate("*********************************************************"); - Debug.appendWithoutDate("IP Reason Time Remaining"); - Debug.appendWithoutDate("*********************************************************"); - - Iterator it = blacklist.keySet().iterator(); - for (; it.hasNext(); ) - { - String ip = it.next(); - BlacklistEntry entry = blacklist.get(ip); - String reason = entry.getBlacklistReason(); - String timeRemaining = entry.getRenderedTimeRemainingOnBlacklist(blacklistMinutes); - - if (ip.length() <= 10) - { - ip += " "; - } - - String row = ip + " " + reason + " " + timeRemaining; - Debug.appendWithoutDate(row); - } - - Debug.appendWithoutDate(""); - } - - private void dumpHashMaps() - { - Debug.appendWithoutDate("hmUserConnectionByIp: " + hmUserConnectionByIpAndPort); - Debug.appendWithoutDate("hmRoomById: " + hmRoomByName); - Debug.appendWithoutDate("hmFunctionsReceivedByMessageType: " + hmFunctionsReceivedByMessageType); - Debug.appendWithoutDate("hmFunctionsHandledByMessageType: " + hmFunctionsHandledByMessageType); - Debug.appendWithoutDate("hmNotificationsAttemptedByNotificationType: " + hmNotificationsAttemptedByNotificationType); - Debug.appendWithoutDate("hmNotificationsSentByNotificationType: " + hmNotificationsSentByNotificationType); - Debug.appendWithoutDate("hmMessagesReceivedByIp: " + hmMessagesReceivedByIp); - Debug.appendWithoutDate("blacklist: " + blacklist); - } - - private void dumpMemory(boolean forceGc) - { - if (forceGc) - { - Debug.append("Forcing a GC before dumping memory..."); - System.gc(); - } - - Debug.appendWithoutDate("-------------------------------------------------------------------------------------------------"); - Debug.appendWithoutDate("Name Max Used Remaining"); - Debug.appendWithoutDate("-------------------------------------------------------------------------------------------------"); - - List memoryList = ManagementFactory.getMemoryPoolMXBeans(); - for (MemoryPoolMXBean tmpMem : memoryList) - { - String name = tmpMem.getName(); - MemoryUsage usage = tmpMem.getUsage(); - long max = usage.getMax(); - long used = usage.getUsed(); - long remaining = max - used; - - String maxStr = (max / (1024 * 1024)) + "Mb"; - String usedStr = (used / (1024 * 1024)) + "Mb"; - String remainingStr = (remaining / (1024 * 1024)) + "Mb"; - - //Metaspace is Java8's version of PermGen. It doesn't have a fixed maximum, it grows to what's needed. - if (name.equals("Metaspace")) - { - maxStr = "N/A"; - remainingStr = "N/A"; - } - - //Don't need everything to end " Space"... - if (name.endsWith(" Space")) - { - int length = name.length(); - name = name.substring(0, length - 6); - } - - //Sort out tabbing so it's a nice looking table - if (name.length() < 10) - { - name += " "; - } - - Debug.appendWithoutDate(name + " " + maxStr + " " + usedStr + " " + remainingStr); - } - - Debug.appendWithoutDate(""); - } - - /** - * KeyListener - */ - @Override - public void keyPressed(KeyEvent arg0) - { - KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(arg0); - int keyCode = keyStroke.getKeyCode(); - if (keyCode == 38) - { - commandLine.setText(lastCommand); - } - } - @Override - public void keyReleased(KeyEvent arg0) {} - @Override - public void keyTyped(KeyEvent arg0) {} + implements ActionListener, + KeyListener, + OnlineConstants, + ServerCommands { + //Statics + private static final int CORE_POOL_SIZE = 50; + private static final int MAX_POOL_SIZE = 500; + private static final int MAX_QUEUE_SIZE = 100; + private static final int KEEP_ALIVE_TIME = 20; + + //Files + private static final Path FILE_PATH_USED_KEYS = Paths.get("C:\\EntropyServer\\UsedKeys.txt"); + private static final Path FILE_PATH_CLIENT_VERSION = Paths.get("C:\\EntropyServer\\Version.txt"); + + //Console + private static DebugConsole console = new DebugConsole(); + + //Caches + private ExtendedConcurrentHashMap hmUserConnectionByIpAndPort = new ExtendedConcurrentHashMap<>(); + private ConcurrentHashMap hmRoomByName = new ConcurrentHashMap<>(); + private ArrayList lobbyMessages = new ArrayList<>(); + private ArrayList usedSymmetricKeys = new ArrayList<>(); + + //Stats stuff + private ConcurrentHashMap hmFunctionsReceivedByMessageType = new ConcurrentHashMap<>(); + private ConcurrentHashMap hmFunctionsHandledByMessageType = new ConcurrentHashMap<>(); + private ConcurrentHashMap hmNotificationsSentByNotificationType = new ConcurrentHashMap<>(); + private ConcurrentHashMap hmNotificationsAttemptedByNotificationType = new ConcurrentHashMap<>(); + private AtomicInteger functionsReceived = new AtomicInteger(0); + private AtomicInteger functionsHandled = new AtomicInteger(0); + private AtomicInteger notificationsAttempted = new AtomicInteger(0); + private AtomicInteger notificationsSent = new AtomicInteger(0); + private AtomicInteger totalFunctionsHandled = new AtomicInteger(0); + private AtomicInteger totalNotificationsSent = new AtomicInteger(0); + private AtomicInteger mostFunctionsReceived = new AtomicInteger(0); + private AtomicInteger mostFunctionsHandled = new AtomicInteger(0); + + //Blacklist + private ConcurrentHashMap hmMessagesReceivedByIp = new ConcurrentHashMap<>(); + private ConcurrentHashMap blacklist = new ConcurrentHashMap<>(); + private boolean usingBlacklist = true; + private int blacklistMinutes = 15; + private int messagesPerSecondThreshold = 15; + + //Tracing + private ArrayList tracedMessages = new ArrayList<>(); + private ArrayList tracedUsers = new ArrayList<>(); + private boolean traceAll = false; + + //Properties + private static boolean devMode = false; + private boolean online = false; + private boolean notificationSocketLogging = false; + + //Seed + private static long currentSeedLong = 4613352884640512L; + + //Thread Pool + private BlockingQueue blockQueue = new ArrayBlockingQueue<>(MAX_QUEUE_SIZE); + private EntropyThreadPoolExecutor tpe = new EntropyThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE, + KEEP_ALIVE_TIME, TimeUnit.SECONDS, blockQueue, this); + + //Other + private String lastCommand = ""; + + private PrivateKey privateKey = null; + + public EntropyServer() { + try { + setTitle("Entropy Server"); + getContentPane().setLayout(new BorderLayout(0, 0)); + getContentPane().add(panel, BorderLayout.NORTH); + panel.add(commandLine); + commandLine.setColumns(13); + panel.add(btnKill); + panel.add(btnThreads); + panel.add(lblFunctionsReceived); + lblFunctionsReceived.setHorizontalAlignment(SwingConstants.CENTER); + lblFunctionsReceived.setPreferredSize(new Dimension(40, 20)); + panel.add(lblFunctionsHandled); + lblFunctionsHandled.setHorizontalAlignment(SwingConstants.CENTER); + lblFunctionsHandled.setPreferredSize(new Dimension(40, 20)); + getContentPane().add(panel_1, BorderLayout.CENTER); + + panel_1.add(btnMemory); + panel_1.add(btnConsole); + panel_1.add(btnSendLogs); + tglbtnScrollLock.setPreferredSize(new Dimension(26, 26)); + tglbtnScrollLock.setIcon(new ImageIcon(EntropyServer.class.getResource("/buttons/key.png"))); + tglbtnScrollLock.setSelectedIcon(new ImageIcon(EntropyServer.class.getResource("/buttons/keySelected.png"))); + panel_1.add(tglbtnScrollLock); + lblNotificationsAttempted.setPreferredSize(new Dimension(40, 20)); + lblNotificationsAttempted.setHorizontalAlignment(SwingConstants.CENTER); + + panel_1.add(lblNotificationsAttempted); + lblNotificationsSent.setPreferredSize(new Dimension(40, 20)); + lblNotificationsSent.setHorizontalAlignment(SwingConstants.CENTER); + + panel_1.add(lblNotificationsSent); + + tglbtnScrollLock.addActionListener(this); + btnThreads.addActionListener(this); + btnKill.addActionListener(this); + commandLine.addActionListener(this); + btnConsole.addActionListener(this); + btnSendLogs.addActionListener(this); + btnMemory.addActionListener(this); + + commandLine.addKeyListener(this); + } catch (Throwable t) { + Debug.stackTrace(t); + } + } + + private final JButton btnKill = new JButton("Kill"); + private final JButton btnThreads = new JButton("Threads"); + private final JTextField commandLine = new JTextField(); + private final JLabel lblFunctionsHandled = new JLabel(""); + private final JLabel lblFunctionsReceived = new JLabel(""); + private final JPanel panel = new JPanel(); + private final JPanel panel_1 = new JPanel(); + private final JButton btnConsole = new JButton("Console"); + private final JButton btnSendLogs = new JButton("Send Logs"); + private final JToggleButton tglbtnScrollLock = new JToggleButton(""); + private final JButton btnMemory = new JButton("Memory"); + private final JLabel lblNotificationsAttempted = new JLabel(""); + private final JLabel lblNotificationsSent = new JLabel(""); + + public static void main(String args[]) { + EntropyServer server = new EntropyServer(); + Thread.setDefaultUncaughtExceptionHandler(new LoggerUncaughtExceptionHandler()); + + //Initialise interfaces etc + EncryptionUtil.setBase64Interface(new Base64Desktop()); + Debug.initialise(console); + + int length = args.length; + for (int i = 0; i < length; i++) { + String arg = args[i]; + applyArgument(arg, server); + } + + server.setSize(420, 110); + server.setResizable(false); + server.setVisible(true); + server.setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); + + server.onStart(); + } + + private static void applyArgument(String arg, EntropyServer server) { + if (arg.equals("devMode")) { + Debug.appendBanner("Running in DEV mode"); + server.setTitle("Entropy Server (DEV)"); + devMode = true; + } + } + + private void onStart() { + try { + Debug.appendBanner("Start-Up"); + + totalNotificationsSent = new AtomicInteger(StatisticsUtil.getTotalNotificationsSent()); + totalFunctionsHandled = new AtomicInteger(StatisticsUtil.getTotalFunctionsHandled()); + mostFunctionsHandled = new AtomicInteger(StatisticsUtil.getMostFunctionsHandled()); + mostFunctionsReceived = new AtomicInteger(StatisticsUtil.getMostFunctionsReceived()); + + readInPrivateKey(); + readUsedKeysFromFile(); + registerDefaultRooms(); + + Debug.append("Starting permanent threads"); + + startInactiveCheckRunnable(); + startBlacklistRunnable(); + startListenerThreads(); + startFunctionThread(); + + toggleOnline(); + + Debug.appendBanner("Server is ready - accepting connections"); + } catch (Throwable t) { + Debug.stackTrace(t, "Caught a thrown exception in onStart()"); + } + } + + private void onStop() { + StatisticsUtil.saveTotalNotificationsSent(totalNotificationsSent); + StatisticsUtil.saveTotalFunctionsHandled(totalFunctionsHandled); + StatisticsUtil.saveMostFunctionsHandled(mostFunctionsHandled); + StatisticsUtil.saveMostFunctionsReceived(mostFunctionsReceived); + + writeUsedKeysToFile(); + } + + private void readInPrivateKey() { + try (InputStream in = getClass().getResourceAsStream("/private.key"); + ObjectInputStream oin = new ObjectInputStream(new BufferedInputStream(in))) { + BigInteger m = (BigInteger) oin.readObject(); + BigInteger e = (BigInteger) oin.readObject(); + RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(m, e); + KeyFactory fact = KeyFactory.getInstance("RSA"); + privateKey = fact.generatePrivate(keySpec); + } catch (Throwable e) { + Debug.stackTrace(e, "COULD NOT READ PRIVATE KEY - SERVER WILL NOT FUNCTION CORRECTLY"); + } + } + + private void registerDefaultRooms() { + Debug.append("Creating rooms..."); + + //Entropy + //2 player + registerNewRoom("Potassium 1", 1, 2, 0, 2); + registerNewRoom("Zinc 1", 1, 2, 2, 2); + registerNewRoom("Helium 1", 1, 2, 4, 3); + registerNewRoom("Magnesium 1", 1, 2, 2, 2, true, true, true, false, false); + registerNewRoom("Cobalt 1", 1, 2, 0, 2, true, true, false, false, false); + registerNewRoom("Chlorine 1", 1, 2, 0, 2, true, true, false, true, false); + registerNewRoom("Gold 1", 1, 2, 0, 2, false, false, true, false, false); + registerNewRoom("Lithium 1", 1, 2, 2, 2, false, false, false, true, true); + registerNewRoom("Beryllium 1", 1, 2, 0, 2, true, true, false, false, true); + + //3 player + registerNewRoom("Bromine 1", 1, 3, 0, 2); + registerNewRoom("Argon 1", 1, 3, 2, 2); + registerNewRoom("Hydrogen 1", 1, 3, 4, 3); + registerNewRoom("Zirconium 1", 1, 3, 2, 2, true, true, true, false, false); + registerNewRoom("Calcium 1", 1, 3, 0, 2, true, true, false, false, false); + registerNewRoom("Iron 1", 1, 3, 2, 2, true, false, false, true, false); + registerNewRoom("Palladium 1", 1, 3, 3, 3, true, true, true, false, true); + + //4 player + registerNewRoom("Nickel 1", 1, 4, 0, 2); + registerNewRoom("Sodium 1", 1, 4, 2, 2); + registerNewRoom("Phosphorus 1", 1, 4, 4, 3); + registerNewRoom("Titanium 1", 1, 4, 0, 2, true, true, false, true, false); + registerNewRoom("Gallium 1", 1, 4, 2, 2, true, true, false, false, true); + + //Vectropy + //2 player + registerNewRoom("Oxygen 1", 2, 2, 0, 2); + registerNewRoom("Neon 1", 2, 2, 2, 2); + registerNewRoom("Copper 1", 2, 2, 4, 3); + registerNewRoom("Manganese 1", 2, 2, 2, 2, true, true, true, false, false); + registerNewRoom("Selenium 1", 2, 2, 0, 2, true, true, false, false, false); + registerNewRoom("Chromium 1", 2, 2, 0, 2, true, true, false, true, false); + registerNewRoom("Silver 1", 2, 2, 0, 2, false, false, true, false, false); + registerNewRoom("Antimony 1", 2, 2, 2, 2, false, false, false, true, true); + registerNewRoom("Tungsten 1", 2, 2, 0, 2, true, true, false, false, true); + + //3 player + registerNewRoom("Carbon 1", 2, 3, 0, 2); + registerNewRoom("Silicon 1", 2, 3, 2, 2); + registerNewRoom("Nitrogen 1", 2, 3, 4, 3); + registerNewRoom("Sulphur 1", 2, 3, 2, 2, true, true, true, false, false); + registerNewRoom("Fluorine 1", 2, 3, 0, 2, true, true, false, false, false); + registerNewRoom("Tin 1", 2, 3, 2, 2, true, false, false, true, false); + registerNewRoom("Indium 1", 2, 3, 3, 3, true, true, true, false, true); + + //4 player + registerNewRoom("Iodine 1", 2, 4, 0, 2); + registerNewRoom("Lead 1", 2, 4, 2, 2); + registerNewRoom("Uranium 1", 2, 4, 4, 3); + registerNewRoom("Vanadium 1", 2, 4, 0, 2, true, true, false, true, false); + registerNewRoom("Xenon 1", 2, 4, 2, 2, true, true, false, false, true); + + Debug.append("Finished creating " + hmRoomByName.size() + " rooms"); + } + + private void startInactiveCheckRunnable() { + InactiveCheckRunnable runnable = new InactiveCheckRunnable(this); + + ServerThread inactiveCheckThread = new ServerThread(runnable, "InactiveCheck"); + inactiveCheckThread.start(); + } + + private void startBlacklistRunnable() { + BlacklistRunnable runnable = new BlacklistRunnable(this); + + ServerThread inactiveCheckThread = new ServerThread(runnable, "BlacklistCheck"); + inactiveCheckThread.start(); + } + + private void startListenerThreads() { + int lowerBound = SERVER_PORT_NUMBER_LOWER_BOUND; + int upperBound = SERVER_PORT_NUMBER_UPPER_BOUND; + + for (int i = lowerBound; i < upperBound; i++) { + try { + ServerThread listenerThread = new ServerThread(new MessageListener(this, i)); + listenerThread.setName("Listener-" + i); + listenerThread.start(); + } catch (Throwable t) { + Debug.stackTrace(t, "Unable to start listener thread on port " + i); + } + } + } + + private void startFunctionThread() { + ServerRunnable functionRunnable = new ServerRunnable() { + private String statusText = ""; + + @Override + public void run() { + while (true) { + statusText = "Updating labels"; + + lblFunctionsReceived.setText("" + functionsReceived); + lblFunctionsHandled.setText("" + functionsHandled); + + lblNotificationsAttempted.setText("" + notificationsAttempted); + lblNotificationsSent.setText("" + notificationsSent); + + clearFunctionStats(); + + try { + statusText = "Sleeping between updates"; + Thread.sleep(1000); + } catch (InterruptedException e) { + Debug.stackTrace(e); + } + } + } + + @Override + public String getDetails() { + return statusText; + } + + @Override + public UserConnection getUserConnection() { + return null; + } + }; + + ServerThread functionThread = new ServerThread(functionRunnable, "CounterThread"); + functionThread.start(); + } + + public int getBlacklistDurationMinutes() { + return blacklistMinutes; + } + + public void executeInWorkerPool(ServerRunnable runnable) { + tpe.executeServerRunnable(runnable); + } + + /** + * Blacklist + */ + public void incrementMessageCountForIp(String ip) { + incrementStatForMessage(ip, hmMessagesReceivedByIp); + + AtomicInteger integer = hmMessagesReceivedByIp.get(ip); + if (integer != null + && integer.intValue() > messagesPerSecondThreshold) { + addToBlacklist(ip, ">" + messagesPerSecondThreshold + " msg/s"); + } + + } + + private void clearIpMessageCounts() { + hmMessagesReceivedByIp = new ConcurrentHashMap<>(); + } + + public void incrementFunctionsReceived() { + functionsReceived.addAndGet(1); + } + + public void incrementFunctionsHandled() { + functionsHandled.addAndGet(1); + totalFunctionsHandled.addAndGet(1); + } + + public void incrementFunctionsReceivedAndHandledForMessage(String message) { + incrementFunctionsReceivedForMessage(message); + incrementFunctionsHandledForMessage(message); + } + + public void incrementFunctionsReceivedForMessage(String message) { + incrementStatForMessage(message, hmFunctionsReceivedByMessageType); + } + + public void incrementFunctionsHandledForMessage(String message) { + incrementStatForMessage(message, hmFunctionsHandledByMessageType); + } + + public void incrementNotificationsSentForMessage(String message) { + incrementStatForMessage(message, hmNotificationsSentByNotificationType); + notificationsSent.addAndGet(1); + totalNotificationsSent.addAndGet(1); + } + + public void incrementNotificationsAttemptedForMessage(String message) { + incrementStatForMessage(message, hmNotificationsAttemptedByNotificationType); + notificationsAttempted.addAndGet(1); + } + + private void incrementStatForMessage(String message, ConcurrentHashMap statsHm) { + AtomicInteger functions = statsHm.get(message); + if (functions == null) { + functions = new AtomicInteger(0); + } + + functions.incrementAndGet(); + statsHm.put(message, functions); + } + + private void clearFunctionStats() { + int functionsReceivedInt = functionsReceived.intValue(); + int functionsHandledInt = functionsHandled.intValue(); + int notificationsAttemptedInt = notificationsAttempted.intValue(); + int notificationsSentInt = notificationsSent.intValue(); + + if (functionsReceivedInt > mostFunctionsReceived.intValue()) { + mostFunctionsReceived.set(functionsReceivedInt); + } + + if (functionsHandledInt > mostFunctionsHandled.intValue()) { + mostFunctionsHandled.set(functionsHandledInt); + } + + int newFunctionsReceived = functionsReceivedInt - functionsHandledInt; + functionsReceived.set(newFunctionsReceived); + functionsHandled.set(0); + + int newNotificationsAttempted = notificationsAttemptedInt - notificationsSentInt; + notificationsAttempted.set(newNotificationsAttempted); + notificationsSent.set(0); + + clearIpMessageCounts(); + } + + public boolean isAlreadyOnline(String username) { + if (username.equalsIgnoreCase("Admin")) { + return true; + } + + return getUserConnectionForUsername(username) != null; + } + + public ArrayList getUserConnectionsForUsernames(HashSet usernames) { + ArrayList uscs = new ArrayList<>(); + + Iterator it = usernames.iterator(); + for (; it.hasNext(); ) { + String username = it.next(); + UserConnection usc = getUserConnectionForUsername(username); + if (usc != null) { + uscs.add(usc); + } + } + + return uscs; + } + + public UserConnection getUserConnectionForUsername(String username) { + Iterator it = hmUserConnectionByIpAndPort.keySet().iterator(); + for (; it.hasNext(); ) { + String ip = it.next(); + UserConnection usc = hmUserConnectionByIpAndPort.get(ip); + String usernameOnUsc = usc.getUsername(); + if (usernameOnUsc != null + && usernameOnUsc.equals(username)) { + return usc; + } + } + + return null; + } + + public void updateMostConcurrentUsers() { + StatisticsUtil.updateMostConcurrentUsers(getCurrentUserCount()); + } + + private int getCurrentUserCount() { + int count = 0; + + Iterator it = hmUserConnectionByIpAndPort.keySet().iterator(); + for (; it.hasNext(); ) { + String ip = it.next(); + UserConnection usc = hmUserConnectionByIpAndPort.get(ip); + String usernameOnUsc = usc.getUsername(); + if (usernameOnUsc != null) { + count++; + } + } + + return count; + } + + public void removeFromUsersOnline(UserConnection usc) { + //Null these out so we don't try to send any more notifications + usc.destroyNotificationSockets(); + + //Cache this key as used so it can't be re-used by someone else in a replay attack + SecretKey symmetricKey = usc.getSymmetricKey(); + cacheKeyAsUsed(symmetricKey); + + //Need to remove them from rooms too + String username = usc.getUsername(); + if (username != null) { + ColourGenerator.freeUpColour(usc.getColour()); + + List rooms = getRooms(); + for (int i = 0; i < rooms.size(); i++) { + Room room = rooms.get(i); + room.removeFromObservers(username); + room.removePlayer(username, false); + } + + Debug.append(username + " has disconnected"); + } + + //Now remove the user connection. + hmUserConnectionByIpAndPort.removeAllWithValue(usc); + if (hmUserConnectionByIpAndPort.isEmpty() + && username != null) { + resetLobby(); + return; + } + + lobbyChanged(); + } + + private void resetLobby() { + int countRemoved = 0; + + List rooms = getRooms(); + int size = rooms.size(); + for (int i = 0; i < size; i++) { + Room room = rooms.get(i); + if (room.getIsCopy()) { + String roomName = room.getRoomName(); + hmRoomByName.remove(roomName); + countRemoved++; + } + } + + //Log out if we've actually removed some rooms + if (countRemoved > 0) { + Debug.append("Removed " + countRemoved + " excess rooms"); + } + + Debug.append("Cleared lobby messages"); + lobbyMessages.clear(); + } + + public void addToChatHistory(String id, String message, String colour, String username) { + OnlineMessage messageObj = new OnlineMessage(colour, message, username); + addToChatHistory(id, messageObj); + } + + public void addAdminMessage(String message) { + OnlineMessage messageObj = new OnlineMessage("black", message, "Admin"); + Iterator it = hmRoomByName.keySet().iterator(); + for (; it.hasNext(); ) { + String name = it.next(); + addToChatHistory(name, messageObj); + } + + addToChatHistory(LOBBY_ID, messageObj); + } + + private void addToChatHistory(String name, OnlineMessage message) { + if (name.equals(LOBBY_ID)) { + lobbyMessages.add(message); + ArrayList usersToNotify = getUserConnections(true); + + Document chatMessage = XmlBuilderServer.getChatNotification(name, message); + sendViaNotificationSocket(usersToNotify, chatMessage, XmlConstants.SOCKET_NAME_CHAT); + } else { + Room room = hmRoomByName.get(name); + room.addToChatHistoryAndNotifyUsers(message); + } + } + + public void lobbyChanged() { + lobbyChanged(null); + } + + public void lobbyChanged(UserConnection userToExclude) { + ArrayList usersToNotify = getUserConnections(true); + if (userToExclude != null) { + usersToNotify.remove(userToExclude); + } + + Document lobbyMessage = XmlUtil.factoryNewDocument(); + XmlBuilderServer.appendLobbyResponse(lobbyMessage, this); + + sendViaNotificationSocket(usersToNotify, lobbyMessage, XmlConstants.SOCKET_NAME_LOBBY); + } + + private void sendViaNotificationSocket(ArrayList uscs, Document message, String socketName) { + sendViaNotificationSocket(uscs, message, socketName, false); + } + + public void sendViaNotificationSocket(ArrayList uscs, Document message, String socketName, boolean blocking) { + AtomicInteger counter = null; + if (blocking) { + int size = uscs.size(); + counter = new AtomicInteger(size); + } + + sendViaNotificationSocket(uscs, message, socketName, counter); + } + + private void sendViaNotificationSocket(ArrayList uscs, Document message, String socketName, AtomicInteger counter) { + int size = uscs.size(); + for (int i = 0; i < size; i++) { + UserConnection usc = uscs.get(i); + usc.sendNotificationInWorkerPool(message, this, socketName, counter); + } + + if (counter != null) { + while (counter.get() > 0) { + //So you wait. You waiiiit. Yoouuu waiiiiiiit. + try { + Thread.sleep(500); + } catch (Throwable t) { + } + } + } + } + + public List getChatHistory(String id) { + if (id.equals(LOBBY_ID)) { + return lobbyMessages; + } + + Room room = hmRoomByName.get(id); + return room.getChatHistory(); + } + + public PrivateKey getPrivateKey() { + return privateKey; + } + + private void registerNewRoom(String roomName, int mode, int players, int jokerQuantity, int jokerValue) { + registerNewRoom(roomName, mode, players, jokerQuantity, jokerValue, false, false, false, false, false); + } + + private Room registerNewRoom(String roomName, int mode, int players, int jokerQuantity, int jokerValue, + boolean includeMoons, boolean includeStars, boolean illegalAllowed, boolean negativeJacks, + boolean cardReveal) { + Iterator it = hmRoomByName.keySet().iterator(); + for (; it.hasNext(); ) { + String id = it.next(); + Room room = hmRoomByName.get(id); + + String nameToCheck = room.getRoomName(); + if (nameToCheck.equals(roomName)) { + Debug.append("Not creating room " + nameToCheck + " as a room with that name already exists."); + return null; + } + } + + Room room = new Room(roomName, mode, players, this); + room.setJokerQuantity(jokerQuantity); + room.setJokerValue(jokerValue); + room.setIncludeMoons(includeMoons); + room.setIncludeStars(includeStars); + room.setIllegalAllowed(illegalAllowed); + room.setNegativeJacks(negativeJacks); + room.setCardReveal(cardReveal); + room.initialiseGame(); + hmRoomByName.put(roomName, room); + + Debug.append("Room created: " + roomName); + return room; + } + + public void registerCopy(Room room) { + String roomName = room.getRoomName(); + int mode = room.getMode(); + int players = room.getPlayers(); + int jokerQuantity = room.getJokerQuantity(); + int jokerValue = room.getJokerValue(); + boolean includeMoons = room.getIncludeMoons(); + boolean includeStars = room.getIncludeStars(); + boolean illegalAllowed = room.getIllegalAllowed(); + boolean negativeJacks = room.getNegativeJacks(); + boolean cardReveal = room.getCardReveal(); + + int index = roomName.indexOf(' ') + 1; + String roomNumberStr = roomName.substring(index); + int roomNumber = Integer.parseInt(roomNumberStr); + + String newRoomName = roomName.substring(0, index) + (roomNumber + 1); + + Room newRoom = registerNewRoom(newRoomName, mode, players, jokerQuantity, jokerValue, + includeMoons, includeStars, illegalAllowed, negativeJacks, cardReveal); + + if (newRoom != null) { + newRoom.setIsCopy(true); + lobbyChanged(); + } + } + + public void removeRoom(String roomName) { + hmRoomByName.remove(roomName); + lobbyChanged(); + } + + public ArrayList getRooms() { + ArrayList list = new ArrayList<>(); + Iterator it = hmRoomByName.keySet().iterator(); + + for (; it.hasNext(); ) { + String id = it.next(); + Room room = hmRoomByName.get(id); + list.add(room); + } + + return list; + } + + public Room getRoomForName(String name) { + return hmRoomByName.get(name); + } + + public void toggleOnline() { + online = !online; + } + + public boolean isOnline() { + return online; + } + + /** + * Generate a secure seed based on: + * - Current time in nanos + * - A private seed known to the Server which is incremented on each hit + * - The total number of notifications that have been sent (generate a random long from this) + */ + public long generateSeed() { + currentSeedLong++; + + Random rand = new Random(totalNotificationsSent.get()); + return System.nanoTime() + currentSeedLong + rand.nextLong(); + } + + public ArrayList getUserConnections(boolean onlyLoggedIn) { + ArrayList uscs = new ArrayList<>(); + + Iterator it = hmUserConnectionByIpAndPort.keySet().iterator(); + for (; it.hasNext(); ) { + String ip = it.next(); + UserConnection usc = hmUserConnectionByIpAndPort.get(ip); + if (onlyLoggedIn + && usc.getUsername() == null) { + continue; + } + + uscs.add(usc); + } + + return uscs; + } + + public UserConnection getUserConnectionForIpAndPort(String ipAndPort) { + return hmUserConnectionByIpAndPort.get(ipAndPort); + } + + public void setUserConnectionForIpAndPort(String ipAndPort, UserConnection usc) { + hmUserConnectionByIpAndPort.put(ipAndPort, usc); + } + + public ArrayList getAllPortsCurrentlyUsedByIp(String ipAddress) { + Iterator it = hmUserConnectionByIpAndPort.keySet().iterator(); + ArrayList list = new ArrayList<>(); + + for (; it.hasNext(); ) { + String ipAndPort = it.next(); + int index = ipAndPort.indexOf("_"); + String ipToCheck = ipAndPort.substring(0, index); + if (ipToCheck.equals(ipAddress)) { + String port = ipAndPort.substring(index + 1); + list.add(port); + } + } + + return list; + } + + public boolean getDevMode() { + return devMode; + } + + public boolean getNotificationSocketLogging() { + return notificationSocketLogging; + } + + @Override + public void actionPerformed(ActionEvent arg0) { + try { + Component source = (Component) arg0.getSource(); + if (source == btnKill) { + onStop(); + + System.exit(0); + } else if (source == btnThreads) { + processCommand(COMMAND_DUMP_THREADS); + } else if (source == btnMemory) { + processCommand("memory"); + } else if (source == commandLine) { + String command = commandLine.getText(); + commandLine.setText(""); + processCommand(command); + } else if (source == btnConsole) { + if (!console.isVisible()) { + console.setTitle("Console"); + console.setSize(1000, 600); + console.setLocationRelativeTo(null); + console.setVisible(true); + } else { + console.toFront(); + } + } else if (source == tglbtnScrollLock) { + boolean scrollLock = tglbtnScrollLock.isSelected(); + console.setScrollLock(scrollLock); + } + } catch (Throwable t) { + Debug.stackTrace(t); + } + } + + public void cacheKeyAsUsed(SecretKey symmetricKey) { + usedSymmetricKeys.add(symmetricKey); + } + + public boolean keyHasAlreadyBeenUsed(SecretKey symmetricKey) { + return usedSymmetricKeys.contains(symmetricKey); + } + + private void writeUsedKeysToFile() { + Charset charset = Charset.forName("US-ASCII"); + try (BufferedWriter writer = Files.newBufferedWriter(FILE_PATH_USED_KEYS, charset)) { + for (SecretKey key : usedSymmetricKeys) { + String word = EncryptionUtil.convertSecretKeyToString(key); + writer.write(word); + writer.newLine(); + } + + //Write out current ones as well + Iterator it = hmUserConnectionByIpAndPort.keySet().iterator(); + for (; it.hasNext(); ) { + String ip = it.next(); + UserConnection usc = hmUserConnectionByIpAndPort.get(ip); + + SecretKey symmetricKey = usc.getSymmetricKey(); + if (symmetricKey != null) { + String word = EncryptionUtil.convertSecretKeyToString(symmetricKey); + writer.write(word); + writer.newLine(); + } + } + } catch (IOException x) { + Debug.stackTrace(x); + } + } + + private void readUsedKeysFromFile() { + try { + Charset charset = Charset.forName("US-ASCII"); + List usedKeys = Files.readAllLines(FILE_PATH_USED_KEYS, charset); + + int keys = 0; + for (String key : usedKeys) { + SecretKey secretKey = EncryptionUtil.reconstructKeyFromString(key); + if (secretKey != null) { + keys++; + usedSymmetricKeys.add(secretKey); + } + } + + Debug.append("Read in " + keys + " used keys"); + } catch (Throwable t) { + Debug.append("Caught " + t + " trying to read in used keys"); + } + } + + public ConcurrentHashMap getBlacklist() { + return blacklist; + } + + public void addToBlacklist(String ipAndPort, String reason) { + ArrayList tokens = StringUtil.getListFromDelims(ipAndPort, "_"); + String ip = tokens.get(0); + + if (usingBlacklist) { + BlacklistEntry entry = new BlacklistEntry(reason); + blacklist.put(ip, entry); + + Debug.append("Blacklisted " + ip + ". Reason: " + reason); + } else { + Debug.append("Not blacklisting " + ip + " because this is turned off. Reason: " + reason); + } + } + + public boolean isBlacklisted(String ipAddress) { + if (!usingBlacklist) { + return false; + } + + BlacklistEntry entry = blacklist.get(ipAddress); + return entry != null; + } + + public boolean messageIsTraced(String nodeName, String username) { + if (traceAll) { + return true; + } + + if (tracedMessages.contains(nodeName)) { + return true; + } + + if (tracedUsers.contains(username)) { + return true; + } + + return false; + } + + private void toggleServerOnline() { + ToggleAvailabilityRunnable runnable = new ToggleAvailabilityRunnable(this); + executeInWorkerPool(runnable); + } + + private void processCommand(String command) { + Debug.append("[Command entered: " + command + "]"); + if (command.equals("help")) { + printCommands(); + } else if (command.equals(COMMAND_DUMP_THREADS)) { + dumpServerThreads(); + } else if (command.equals(COMMAND_DUMP_THREAD_STACKS)) { + dumpThreadStacks(); + } else if (command.equals(COMMAND_DUMP_USERS)) { + Debug.append(getCurrentUserCount() + " user(s) online:"); + Iterator it = hmUserConnectionByIpAndPort.keySet().iterator(); + for (; it.hasNext(); ) { + String ip = it.next(); + UserConnection usc = hmUserConnectionByIpAndPort.get(ip); + Debug.appendWithoutDate("" + usc); + } + } else if (command.equals(COMMAND_TRACE_ALL)) { + traceAll = !traceAll; + Debug.append("Tracing all messages: " + traceAll); + } else if (command.startsWith(COMMAND_TRACE_USER)) { + String username = command.substring(COMMAND_TRACE_USER.length()); + if (tracedUsers.contains(username)) { + Debug.append("Stopped tracing user " + username); + tracedUsers.remove(username); + } else { + Debug.append("Tracing user " + username); + tracedUsers.add(username); + } + } else if (command.startsWith(COMMAND_TRACE_MESSAGE_AND_RESPONSE)) { + String messageStr = command.substring(COMMAND_TRACE_MESSAGE_AND_RESPONSE.length()); + + if (tracedMessages.contains(messageStr)) { + Debug.append("Stopped tracing " + messageStr + " and its responses"); + tracedMessages.remove(messageStr); + } else { + Debug.append("Tracing message " + messageStr + " and its responses"); + tracedMessages.add(messageStr); + } + } else if (command.equals(COMMAND_SHUT_DOWN)) { + toggleServerOnline(); + } else if (command.startsWith(COMMAND_RESET)) { + String roomIdStr = command.substring(COMMAND_RESET.length()); + Room room = hmRoomByName.get(roomIdStr); + if (room == null) { + Debug.append("No room found for ID " + roomIdStr); + return; + } + + Debug.append("Resetting currentPlayers for room " + roomIdStr); + room.resetCurrentPlayers(); + } else if (command.equals(COMMAND_CLEAR_ROOMS)) { + if (!hmUserConnectionByIpAndPort.isEmpty()) { + Debug.append("Not clearing down rooms as there are active user connections."); + return; + } + + resetLobby(); + } else if (command.equals(COMMAND_DUMP_STATS)) { + Debug.appendBanner("STATS DUMP"); + Debug.appendWithoutDate("Most messages received per second: " + mostFunctionsReceived.get()); + Debug.appendWithoutDate("Most messages handled per second: " + mostFunctionsHandled.get()); + Debug.appendWithoutDate("Total Messages Handled: " + totalFunctionsHandled.get()); + Debug.appendWithoutDate("Total Notifications Sent: " + totalNotificationsSent.get()); + Debug.appendWithoutDate("Most concurrent users: " + StatisticsUtil.getMostConcurrentUsers()); + StatisticsUtil.doGlobalStatsDump(); + Debug.appendWithoutDate(""); + } else if (command.equals(COMMAND_MESSAGE_STATS)) { + dumpMessageStats(); + } else if (command.equals(COMMAND_DECRYPTION_LOGGING)) { + EncryptionUtil.failedDecryptionLogging = !EncryptionUtil.failedDecryptionLogging; + Debug.appendWithoutDate("Decryption logging: " + EncryptionUtil.failedDecryptionLogging); + } else if (command.equals(COMMAND_CLEAR_STATS)) { + StatisticsUtil.clearServerData(); + + totalFunctionsHandled.set(0); + mostFunctionsHandled.set(0); + mostFunctionsReceived.set(0); + } else if (command.startsWith(COMMAND_SET_CORE_POOL_SIZE)) { + int newCoreSize = parseArgumentAsInt(command, COMMAND_SET_CORE_POOL_SIZE); + if (newCoreSize > -1) { + tpe.setCorePoolSize(newCoreSize); + Debug.append("Core pool size is now " + newCoreSize); + } + } else if (command.startsWith(COMMAND_SET_MAX_POOL_SIZE)) { + int newMaxSize = parseArgumentAsInt(command, COMMAND_SET_MAX_POOL_SIZE); + if (newMaxSize > -1) { + tpe.setMaximumPoolSize(newMaxSize); + Debug.append("Max pool size is now " + newMaxSize); + } + } else if (command.startsWith(COMMAND_SET_KEEP_ALIVE_TIME)) { + String newKeepAliveTime = command.substring(COMMAND_SET_KEEP_ALIVE_TIME.length()); + tpe.setKeepAliveTime(Integer.parseInt(newKeepAliveTime), TimeUnit.SECONDS); + Debug.append("Keep Alive Time is now " + newKeepAliveTime + "s"); + } else if (command.equals(COMMAND_POOL_STATS)) { + Debug.appendWithoutDate("-----------------------------------------"); + Debug.appendWithoutDate("Max size: " + tpe.getMaximumPoolSize()); + Debug.appendWithoutDate("Core size: " + tpe.getCorePoolSize()); + Debug.appendWithoutDate("Alive time: " + tpe.getKeepAliveTime(TimeUnit.SECONDS)); + Debug.appendWithoutDate("-----------------------------------------"); + Debug.appendWithoutDate("Current queue size: " + blockQueue.size()); + Debug.appendWithoutDate("Remaining queue capacity: " + blockQueue.remainingCapacity()); + Debug.appendWithoutDate("Active threads: " + tpe.getActiveCount()); + Debug.appendWithoutDate("Pool size: " + tpe.getPoolSize()); + Debug.appendWithoutDate("Largest pool size: " + tpe.getLargestPoolSize()); + Debug.appendWithoutDate("Completion status: " + tpe.getCompletedTaskCount() + " / " + tpe.getTaskCount()); + Debug.appendWithoutDate("-----------------------------------------"); + } else if (command.startsWith(COMMAND_FAKE_USERS)) { + if (!devMode) { + Debug.append("Not running command as not running in DEV mode"); + return; + } + + int users = parseArgumentAsInt(command, COMMAND_FAKE_USERS); + for (int i = 0; i < users; i++) { + UserConnection usc = new UserConnection(null, null); + usc.update("Fake " + i, false); + setUserConnectionForIpAndPort("" + i, usc); + } + } else if (command.equals(COMMAND_DUMP_BLACKLIST)) { + dumpBlacklist(); + } else if (command.equals(COMMAND_BLACKLIST_FULL)) { + usingBlacklist = true; + blacklistMinutes = -1; + Debug.appendBanner("Blacklist is FULL"); + } else if (command.equals(COMMAND_BLACKLIST_OFF)) { + usingBlacklist = false; + Debug.appendBanner("Blacklist is OFF"); + } else if (command.startsWith(COMMAND_BLACKLIST_TIME)) { + int minutes = parseArgumentAsInt(command, COMMAND_BLACKLIST_TIME); + + if (minutes > -1) { + blacklistMinutes = minutes; + usingBlacklist = true; + Debug.appendBanner("Blacklist is ON and set to " + minutes + " minutes"); + } + } else if (command.startsWith(COMMAND_BLACKLIST)) { + String ip = command.substring(COMMAND_BLACKLIST.length()); + addToBlacklist(ip, "Manual"); + } else if (command.startsWith(COMMAND_SET_BLACKLIST_THRESHOLD)) { + int threshold = parseArgumentAsInt(command, COMMAND_SET_BLACKLIST_THRESHOLD); + if (threshold > -1) { + messagesPerSecondThreshold = threshold; + Debug.append("Will now blacklist anyone who sends more than " + threshold + " message/s"); + } + } else if (command.equals(COMMAND_USED_KEYS)) { + for (SecretKey key : usedSymmetricKeys) { + String keyStr = EncryptionUtil.convertSecretKeyToString(key); + Debug.appendWithoutDate(keyStr); + } + } else if (command.equals(COMMAND_DUMP_HASH_MAPS)) { + dumpHashMaps(); + } else if (command.startsWith(COMMAND_MEMORY)) { + boolean forceGc = false; + + int index = command.indexOf(" "); + if (index > -1) { + String gcBool = command.substring((COMMAND_MEMORY + " ").length()); + forceGc = gcBool.equals("true"); + } + + dumpMemory(forceGc); + } else if (command.startsWith(COMMAND_NOTIFICATION_LOGGING)) { + notificationSocketLogging = !notificationSocketLogging; + Debug.append("Notification logging: " + notificationSocketLogging); + } else if (command.startsWith(COMMAND_NOTIFY_USER)) { + String username = command.substring(COMMAND_NOTIFY_USER.length()); + UserConnection usc = getUserConnectionForUsername(username); + if (usc == null) { + Debug.append("Failed to find usc for " + username); + return; + } + + usc.notifySockets(); + } else if (command.equals(COMMAND_SERVER_VERSION)) { + Debug.append("Server version: " + OnlineConstants.SERVER_VERSION); + } else { + Debug.append("Unrecognised command - type 'help' for a list of available commands"); + return; + } + + lastCommand = command; + } + + private int parseArgumentAsInt(String fullCommand, String prefix) { + int ret = -1; + String integerStr = fullCommand.substring(prefix.length()); + try { + ret = Integer.parseInt(integerStr); + } catch (NumberFormatException nfe) { + Debug.append("Failed to parse " + integerStr + " as int"); + } + + return ret; + } + + private void printCommands() { + Debug.append("The available commands are:"); + Debug.appendWithoutDate(COMMAND_DUMP_THREADS); + Debug.appendWithoutDate(COMMAND_DUMP_THREAD_STACKS); + Debug.appendWithoutDate(COMMAND_DUMP_USERS); + Debug.appendWithoutDate(COMMAND_TRACE_ALL); + Debug.appendWithoutDate(COMMAND_TRACE_USEFUL); + Debug.appendWithoutDate(COMMAND_TRACE_USER + ""); + Debug.appendWithoutDate(COMMAND_TRACE_MESSAGE_AND_RESPONSE + ""); + Debug.appendWithoutDate(COMMAND_SHUT_DOWN); + Debug.appendWithoutDate(COMMAND_RESET + ""); + Debug.appendWithoutDate(COMMAND_CLEAR_ROOMS); + Debug.appendWithoutDate(COMMAND_DUMP_STATS); + Debug.appendWithoutDate(COMMAND_MESSAGE_STATS); + Debug.appendWithoutDate(COMMAND_DECRYPTION_LOGGING); + Debug.appendWithoutDate(COMMAND_CLEAR_STATS); + Debug.appendWithoutDate(COMMAND_LAUNCH_DAY); + Debug.appendWithoutDate(COMMAND_POOL_STATS); + Debug.appendWithoutDate(COMMAND_SET_CORE_POOL_SIZE + ""); + Debug.appendWithoutDate(COMMAND_SET_MAX_POOL_SIZE + ""); + Debug.appendWithoutDate(COMMAND_SET_KEEP_ALIVE_TIME + ""); + Debug.appendWithoutDate(COMMAND_DUMP_BLACKLIST); + Debug.appendWithoutDate(COMMAND_BLACKLIST_TIME + ""); + Debug.appendWithoutDate(COMMAND_BLACKLIST_FULL); + Debug.appendWithoutDate(COMMAND_BLACKLIST_OFF); + Debug.appendWithoutDate(COMMAND_BLACKLIST + ""); + Debug.appendWithoutDate(COMMAND_SET_BLACKLIST_THRESHOLD + ""); + Debug.appendWithoutDate(COMMAND_USED_KEYS); + Debug.appendWithoutDate(COMMAND_DUMP_HASH_MAPS); + Debug.appendWithoutDate(COMMAND_MEMORY + ""); + Debug.appendWithoutDate(COMMAND_NOTIFICATION_LOGGING); + Debug.appendWithoutDate(COMMAND_NOTIFY_USER + ""); + Debug.appendWithoutDate(COMMAND_SERVER_VERSION); + } + + private void dumpMessageStats() { + dumpMessageStats("Message Received Handled", hmFunctionsReceivedByMessageType, hmFunctionsHandledByMessageType); + dumpMessageStats("Notification Attempted Sent", hmNotificationsAttemptedByNotificationType, hmNotificationsSentByNotificationType); + } + + private void dumpMessageStats(String titles, ConcurrentHashMap hmReceived, + ConcurrentHashMap hmHandled) { + if (hmReceived.isEmpty()) { + return; + } + + Debug.appendWithoutDate("*********************************************************"); + Debug.appendWithoutDate(titles); + Debug.appendWithoutDate("*********************************************************"); + + int totalReceived = 0; + int totalHandled = 0; + + Iterator it = hmReceived.keySet().iterator(); + for (; it.hasNext(); ) { + String message = it.next(); + AtomicInteger functionsReceived = hmReceived.get(message); + AtomicInteger functionsHandled = hmHandled.get(message); + if (functionsHandled == null) { + functionsHandled = new AtomicInteger(0); + } + + totalReceived += functionsReceived.get(); + totalHandled += functionsHandled.get(); + + if (message.length() < 10 + || message.equals("ClientMail")) { + message += " "; + } + + String row = message + " " + functionsReceived + " " + functionsHandled; + Debug.appendWithoutDate(row); + } + + Debug.appendWithoutDate("*********************************************************"); + Debug.appendWithoutDate("Total " + totalReceived + " " + totalHandled); + Debug.appendWithoutDate("*********************************************************"); + + Debug.appendWithoutDate(""); + } + + private void dumpBlacklist() { + if (blacklist.isEmpty()) { + return; + } + + Debug.appendWithoutDate("*********************************************************"); + Debug.appendWithoutDate("IP Reason Time Remaining"); + Debug.appendWithoutDate("*********************************************************"); + + Iterator it = blacklist.keySet().iterator(); + for (; it.hasNext(); ) { + String ip = it.next(); + BlacklistEntry entry = blacklist.get(ip); + String reason = entry.getBlacklistReason(); + String timeRemaining = entry.getRenderedTimeRemainingOnBlacklist(blacklistMinutes); + + if (ip.length() <= 10) { + ip += " "; + } + + String row = ip + " " + reason + " " + timeRemaining; + Debug.appendWithoutDate(row); + } + + Debug.appendWithoutDate(""); + } + + private void dumpHashMaps() { + Debug.appendWithoutDate("hmUserConnectionByIp: " + hmUserConnectionByIpAndPort); + Debug.appendWithoutDate("hmRoomById: " + hmRoomByName); + Debug.appendWithoutDate("hmFunctionsReceivedByMessageType: " + hmFunctionsReceivedByMessageType); + Debug.appendWithoutDate("hmFunctionsHandledByMessageType: " + hmFunctionsHandledByMessageType); + Debug.appendWithoutDate("hmNotificationsAttemptedByNotificationType: " + hmNotificationsAttemptedByNotificationType); + Debug.appendWithoutDate("hmNotificationsSentByNotificationType: " + hmNotificationsSentByNotificationType); + Debug.appendWithoutDate("hmMessagesReceivedByIp: " + hmMessagesReceivedByIp); + Debug.appendWithoutDate("blacklist: " + blacklist); + } + + private void dumpMemory(boolean forceGc) { + if (forceGc) { + Debug.append("Forcing a GC before dumping memory..."); + System.gc(); + } + + Debug.appendWithoutDate("-------------------------------------------------------------------------------------------------"); + Debug.appendWithoutDate("Name Max Used Remaining"); + Debug.appendWithoutDate("-------------------------------------------------------------------------------------------------"); + + List memoryList = ManagementFactory.getMemoryPoolMXBeans(); + for (MemoryPoolMXBean tmpMem : memoryList) { + String name = tmpMem.getName(); + MemoryUsage usage = tmpMem.getUsage(); + long max = usage.getMax(); + long used = usage.getUsed(); + long remaining = max - used; + + String maxStr = (max / (1024 * 1024)) + "Mb"; + String usedStr = (used / (1024 * 1024)) + "Mb"; + String remainingStr = (remaining / (1024 * 1024)) + "Mb"; + + //Metaspace is Java8's version of PermGen. It doesn't have a fixed maximum, it grows to what's needed. + if (name.equals("Metaspace")) { + maxStr = "N/A"; + remainingStr = "N/A"; + } + + //Don't need everything to end " Space"... + if (name.endsWith(" Space")) { + int length = name.length(); + name = name.substring(0, length - 6); + } + + //Sort out tabbing so it's a nice looking table + if (name.length() < 10) { + name += " "; + } + + Debug.appendWithoutDate(name + " " + maxStr + " " + usedStr + " " + remainingStr); + } + + Debug.appendWithoutDate(""); + } + + /** + * KeyListener + */ + @Override + public void keyPressed(KeyEvent arg0) { + KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(arg0); + int keyCode = keyStroke.getKeyCode(); + if (keyCode == 38) { + commandLine.setText(lastCommand); + } + } + + @Override + public void keyReleased(KeyEvent arg0) { + } + + @Override + public void keyTyped(KeyEvent arg0) { + } }