WONDERGG
WONDERGG

Reputation: 125

Run a loop everytime a Point is added to an Arraylist (Java)

I'm currently working in following class, a paint program with a while loop sending packages from client to server:

public class TCPClient extends JPanel {


    public static ArrayList<Point> location = new ArrayList<>();

    private JTextArea consoleOutput = new JTextArea(1,20);

    public void addComponentToPane(Container pane) {
        consoleOutput.setEditable(false);
    }

    public TCPClient() {
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                location.add(e.getPoint());
            }
        });

        addMouseMotionListener(new MouseMotionAdapter() {
            @Override
            public void mouseDragged(MouseEvent e) {
                location.add(e.getPoint());
                repaint();
            }
        });
        setPreferredSize(new Dimension(800, 500));
        setBackground(Color.WHITE);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        if(location.isEmpty()){
            return;
        }

        Point p = location.get(0);
        for (int i = 1; i < location.size(); i++) {
            Point q = location.get(i);
            g.drawLine(p.x, p.y, q.x, q.y);
            p = q;
        }
    }

    public static void main(String argv[])  throws Exception {

        InetAddress SERVERIP = InetAddress.getLocalHost();

        JFrame frame = new JFrame("Drawing with friends");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new TCPClient(), BorderLayout.CENTER);

        JTextArea IPadress = new JTextArea(1,20);
        IPadress.setEditable(false);
        IPadress.append("DEVICE IP: " + SERVERIP.getHostAddress());
        frame.add(IPadress, BorderLayout.SOUTH);

        frame.setSize(new Dimension(800,600));
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setVisible(true);

            while(true) {
                    try {
                        //BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));
                        Socket clientSocket = new Socket("localhost", 9000);

                        ObjectOutputStream outToServer = new ObjectOutputStream(clientSocket.getOutputStream());

                        //ObjectInputStream inFromServer = new ObjectInputStream(clientSocket.getInputStream());

                        outToServer.writeObject(location);

                        outToServer.flush();
                        clientSocket.close();

                        Thread.sleep(100);

                    } catch (SocketException e) {
                        System.err.println(e.toString());         
            }
        }
    }
}

As it my while loop runs constantly with a sleep(100) every loop. This works as intended, however for effeciency I want the loop to run everytime something is changed in my ArrayList locations, so it doesn't send irrellevant packages.

What I'm trying to achieve:

if(change in location) {
    Send information to server
}

Upvotes: 1

Views: 106

Answers (4)

Nicolas Filotto
Nicolas Filotto

Reputation: 44995

You will need to wrap any accesses (read and write) to your variable location into a synchronized block with a notify() in case you update it and then in your loop you replace the sleep with a wait as next:

Modification of location:

synchronized (location) {
    location.add(e.getPoint());
    location.notify();
}

Read access to location:

synchronized (location) {
    if(location.isEmpty()){
        return;
    }

    Point p = location.get(0);
    for (int i = 1; i < location.size(); i++) {
        Point q = location.get(i);
        g.drawLine(p.x, p.y, q.x, q.y);
        p = q;
    }
}

Final loop inside a synchronized block:

synchronized (location) {
    while(true) {
        //BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));
        Socket clientSocket = new Socket("localhost", 9000);

        ObjectOutputStream outToServer = new ObjectOutputStream(clientSocket.getOutputStream());

        //ObjectInputStream inFromServer = new ObjectInputStream(clientSocket.getInputStream());

        outToServer.writeObject(location);

        outToServer.flush();
        clientSocket.close();
        location.wait();
    }
}

Upvotes: 1

MadProgrammer
MadProgrammer

Reputation: 347314

Basically, you should use some kind of BlockingQueue, this will allow your "socket" thread to "block" while waiting for new Points to arrive.

Instead of sending the whole List, you should instead, send each Point individually, this will save on time as the number of points grows.

Because of the nature of the queue, you can keep adding more points to it and the "socket" thread can process them at it's own pace, this will allow the queue to increase in size as the user draws, but allow the "socket" thread to catch up when the user stops, without your doing anything special.

The following example uses a Thread.sleep to generate an artificial delay between the processing of each point from the queue for demonstration purposes and, obviously, you shouldn't use it (Thread.sleep), but it demonstrates the points above, the thread will continue to dump points from the queue to the stdout after you stop drawing

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    private BlockingQueue<Point> queue;

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                queue = new LinkedBlockingQueue<>();

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TCPClient(queue));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

                Thread t = new Thread(new Consumer(queue));
                t.setDaemon(true);
                t.start();
            }
        });
    }

    public class TCPClient extends JPanel {

        private JTextArea consoleOutput = new JTextArea(1, 20);
        private Queue<Point> queue;
        private List<Point> cache;

        public TCPClient(Queue<Point> queue) {
            this.queue = queue;
            cache = new ArrayList<>(25);
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    queue.add(e.getPoint());
                    cache.add(e.getPoint());
                }
            });

            addMouseMotionListener(new MouseMotionAdapter() {
                @Override
                public void mouseDragged(MouseEvent e) {
                    queue.add(e.getPoint());
                    cache.add(e.getPoint());
                    repaint();
                }
            });
            setPreferredSize(new Dimension(800, 500));
            setBackground(Color.WHITE);
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);

            if (cache.isEmpty()) {
                return;
            }

            Point p = cache.get(0);
            for (int i = 1; i < cache.size(); i++) {
                Point q = cache.get(i);
                g.drawLine(p.x, p.y, q.x, q.y);
                p = q;
            }
        }
    }

    public class Consumer implements Runnable {

        private BlockingQueue<Point> queue;

        public Consumer(BlockingQueue<Point> queue) {
            this.queue = queue;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Point p = queue.poll(Long.MAX_VALUE, TimeUnit.DAYS);
                    if (p != null) {
                        System.out.println("-> Got " + p);
                        Thread.sleep(125);
                    }
                } catch (InterruptedException ex) {
                }
            }
        }

    }
}

Upvotes: 1

QBrute
QBrute

Reputation: 4544

You could create your own CallbackArrayList, which extends ArrayList. Add abstract callback methods such as onAdded and onRemoved.

Override those methods of ArrayList which you want to monitor and call your callback methods inside, depending on the result.

abstract class CallbackArrayList<T> extends ArrayList<T> {
  public abstract void onAddSuccess(T object);
  public abstract void onAddFailure(T object);
  @Override
  public boolean add(T object) {
    boolean success = super.add(object);
    if(success) {
      onAddSuccess(object);
    }
    else {
      onAddFailure(object);
    }
    return success;
  }
}

Then when assigning your list you can do

location = new CallbackArrayList<>() {
  @Override
  public void onAddSuccess(Point object) {
    // handle send information to server
  }
  @Override
  public void onAddFailure(Point object) {
    // handle failure
  }
};

Whenever you call location.add(e.getPoint()), one of the callback methods are called afterwards.

Upvotes: 0

B&#225;lint
B&#225;lint

Reputation: 4049

Instead of allowing other objects to modify your ArrayList directly, make it private and create a getter, a setter, and also (because you're working with collections) an adder and a remover method.

Inside those, you can notify the other parts of the program, that the ArrayList was changed, and you need to send a packet.

Upvotes: 0

Related Questions