diff --git a/.gitignore b/.gitignore index effed228619b104704cdac55d86d1a82b94f0443..e98a0ab5b27832c555a18e975ec7ed8fd22cb167 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /KvizServer/.idea/codeStyles/Project.xml /KvizServer/.idea/caches/build_file_checksums.ser /KvizClient/.idea/codeStyles/Project.xml +/KvizClient/app/src/main/java/onlab/kvizclient/LobbyActivity.java.orig diff --git a/KvizClient/app/src/main/java/onlab/kvizclient/MultipleChoiceFragment.java b/KvizClient/app/src/main/java/onlab/kvizclient/MultipleChoiceFragment.java index ed230c6d0f65ee933f0b0c0463bb9ca170aba1aa..09bfd7df01d108589c647c786fc7ee1cc4e9627e 100644 --- a/KvizClient/app/src/main/java/onlab/kvizclient/MultipleChoiceFragment.java +++ b/KvizClient/app/src/main/java/onlab/kvizclient/MultipleChoiceFragment.java @@ -169,13 +169,15 @@ public class MultipleChoiceFragment extends Fragment { LinearLayout multipleChoiceFragmentLinearLayout = (LinearLayout) getView().findViewById(R.id.MultipleChoiceFragmentLinearLayout); for (int i=0;i<playerAnswers.size();i++) { if (i != own_index) { - final TextView playerAnswerTextView = new TextView(getActivity()); PlayerAnswer answer = playerAnswers.get(i); - playerAnswerTextView.setText(answer.getPlayerName() + ": " - + answers[answer.getValue()]); - playerAnswerTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); - playerAnswerTextView.setPadding(20,30,20,0); - multipleChoiceFragmentLinearLayout.addView(playerAnswerTextView); + if (answer.getValue() > -1) { + final TextView playerAnswerTextView = new TextView(getActivity()); + playerAnswerTextView.setText(answer.getPlayerName() + ": " + + answers[answer.getValue()]); + playerAnswerTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); + playerAnswerTextView.setPadding(20,30,20,0); + multipleChoiceFragmentLinearLayout.addView(playerAnswerTextView); + } } } } diff --git a/KvizObserver/pom.xml b/KvizObserver/pom.xml index b6fbe3e0238820437e49df79d008dd07913e53e4..c1eafbe517dd083210ffe3cb4166fe88f22fcb6e 100644 --- a/KvizObserver/pom.xml +++ b/KvizObserver/pom.xml @@ -66,6 +66,11 @@ The project was originally started in December 2002 by Arthur van Hoff at Strang <version>3.3.1</version> <scope>test</scope> </dependency> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <version>2.3</version> + </dependency> </dependencies> <build> <resources> diff --git a/KvizObserver/src/images/arrow_left.png b/KvizObserver/src/images/arrow_left.png new file mode 100644 index 0000000000000000000000000000000000000000..725e7aab6412baf1527c2a98e9293f29423ab59e Binary files /dev/null and b/KvizObserver/src/images/arrow_left.png differ diff --git a/KvizObserver/src/images/arrow_right.png b/KvizObserver/src/images/arrow_right.png new file mode 100644 index 0000000000000000000000000000000000000000..35f1a1f72c8bc79d951bf0f8de9ffdbc61e05177 Binary files /dev/null and b/KvizObserver/src/images/arrow_right.png differ diff --git a/KvizObserver/src/main/java/FontResizable.java b/KvizObserver/src/main/java/FontResizable.java new file mode 100644 index 0000000000000000000000000000000000000000..80e3877dfc28f59d3a58ca20fd99b0152c8add83 --- /dev/null +++ b/KvizObserver/src/main/java/FontResizable.java @@ -0,0 +1,3 @@ +public interface FontResizable { + public void setFontSize(int fontSize); +} diff --git a/KvizObserver/src/main/java/Main.java b/KvizObserver/src/main/java/Main.java index 46b73f53aefedf69f8639f302cd666847be155dc..3f0bb70ba310cf5fb5ca42ec01763ee98c701a57 100644 --- a/KvizObserver/src/main/java/Main.java +++ b/KvizObserver/src/main/java/Main.java @@ -10,9 +10,6 @@ import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; -import java.util.Scanner; -import java.util.concurrent.Callable; -import java.util.concurrent.FutureTask; import javax.jmdns.JmDNS; import javax.jmdns.ServiceEvent; @@ -21,8 +18,12 @@ import javax.jmdns.ServiceListener; import javax.swing.*; import javax.swing.border.EmptyBorder; +import com.google.gson.Gson; +import model.PlayerAnswer; import model.ServerModel; +import java.util.List; + public class Main { private String TAG = "Client"; @@ -49,6 +50,7 @@ public class Main { private static JFrame frame; private static JPanel mainPanel; private static JPanel serverListPanel; + private static FontResizable actualFontResizable = null; private static class SampleListener implements ServiceListener { @Override @@ -113,71 +115,65 @@ public class Main { threads = new ArrayList<Thread>(); frame = new JFrame("KvizObserver"); - mainPanel = new JPanel(new CardLayout()); + mainPanel = new JPanel(new BorderLayout()); JPanel observerNamePanel = new JPanel(); + observerNamePanel.setBorder(new EmptyBorder(20, 20, 20, 20)); observerNamePanel.setLayout(new BoxLayout(observerNamePanel, BoxLayout.Y_AXIS)); JLabel observerNameLabel = new JLabel("Observer name:"); observerNamePanel.add(observerNameLabel); JTextField observerNameTextField = new JTextField(); + observerNameTextField.setMaximumSize(new Dimension(200, 100)); observerNamePanel.add(observerNameTextField); JButton startSearchingButton = new JButton("Start searching servers"); startSearchingButton.addActionListener(new StartSearchingButtonListener(observerNameTextField)); observerNamePanel.add(startSearchingButton); - mainPanel.add(observerNamePanel, "OBSERVER_NAME_PANEL"); + mainPanel.add(observerNamePanel); serverListPanel = new JPanel(); serverListPanel.setLayout(new BoxLayout(serverListPanel, BoxLayout.Y_AXIS)); - mainPanel.add(serverListPanel, "SERVER_LIST_PANEL"); + + JMenuItem fontSizeMenuItem = new JMenuItem("Font size"); + fontSizeMenuItem.addActionListener(new setFontSizeButtonListener()); // adding listener + JMenu menu = new JMenu("Menu"); + menu.add(fontSizeMenuItem); + JMenuBar bar = new JMenuBar(); + bar.add(menu); + frame.setJMenuBar(bar); frame.add(mainPanel, BorderLayout.NORTH); - frame.setPreferredSize(new Dimension(300, 200)); + frame.setPreferredSize(new Dimension(600, 400)); frame.pack(); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); - //try { - - - //System.out.println("Set The ObserverName:"); - //Scanner s = new Scanner(System.in); - //CLIENT_NAME = s.nextLine(); - - - // Create a JmDNS instance - //JmDNS jmdns = JmDNS.create(InetAddress.getLocalHost()); - - - // Add a service listener - //jmdns.addServiceListener("_http._tcp.local.", new SampleListener()); - // Wait a bit - //Thread.sleep(3000); - - //String msg = ""; - - - /*Scanner s = new Scanner(System.in); - System.out.println("Type 'Connect' to Connect:"); - msg = s.nextLine(); - if(msg.equals("Connect")) - { - onConnect(0); - } - System.out.println("Type 'Disconnect' to Disconnect:"); - msg = s.nextLine(); - if(msg.equals("Disconnect")) - { - onDisconnect(); - }*/ - /*} catch (UnknownHostException e) { - System.out.println(e.getMessage()); - } catch (IOException e) { - System.out.println(e.getMessage());*/ - //} - + //teszteléshez + /* + PlayerAnswer pla = new PlayerAnswer("játékos"); + pla.setValue(2); + pla.setTime(3.3); + PlayerAnswer pla2 = new PlayerAnswer("játékos2"); + pla2.setValue(2); + pla2.setTime(3.3); + PlayerAnswer pla3 = new PlayerAnswer("játékos3"); + pla3.setValue(2); + pla3.setTime(3.3); + List<PlayerAnswer> plalist = new ArrayList<>(); + plalist.add(pla); + plalist.add(pla2); + plalist.add(pla3); + Gson gson = new Gson(); + MultipleChoiceQuestionPanel mcqp = new MultipleChoiceQuestionPanel("Melyik a leghosszabb szó? Melyik? Melyik? Melyik? Melyik?", "alma", + "paradicsom", "görögdinnye", "őszibarack", gson.toJson(plalist), 1, -1); + actualFontResizable = mcqp; + mainPanel.removeAll(); + mainPanel.add(mcqp); + mainPanel.revalidate(); + mainPanel.repaint(); + */ } final static class StartSearchingButtonListener implements ActionListener { @@ -187,8 +183,10 @@ public class Main { } public void actionPerformed(ActionEvent ae) { CLIENT_NAME = tf.getText(); - CardLayout cl = (CardLayout)(mainPanel.getLayout()); - cl.show(mainPanel, "SERVER_LIST_PANEL"); + mainPanel.removeAll(); + mainPanel.add(serverListPanel); + mainPanel.revalidate(); + mainPanel.repaint(); try { // Create a JmDNS instance @@ -260,6 +258,25 @@ public class Main { } } + static class setFontSizeButtonListener implements ActionListener { + @Override + public void actionPerformed(ActionEvent e) { + String s = (String) JOptionPane.showInputDialog( + frame, + "Font size: ", + "Set font size", + JOptionPane.PLAIN_MESSAGE, + null, + null,null); + + if (s != null) { + actualFontResizable.setFontSize(Integer.parseInt(s)); + System.out.println("Új betűméret: " + s); + return; + } + } + } + class CommunicationThread implements Runnable { private ServerModel serverModel; @@ -328,6 +345,17 @@ public class Main { exit = false; //majd break; + case "questionMC": + case "answerMC": + MultipleChoiceQuestionPanel mcqp = new MultipleChoiceQuestionPanel(params[1], params[2], + params[3], params[4], params[5], params[6], Integer.parseInt(params[7]), + Integer.parseInt(params[8])); + actualFontResizable = mcqp; + mainPanel.removeAll(); + mainPanel.add(mcqp); + mainPanel.revalidate(); + mainPanel.repaint(); + break; case "Disconnect": break; default: diff --git a/KvizObserver/src/main/java/MultipleChoiceQuestionPanel.java b/KvizObserver/src/main/java/MultipleChoiceQuestionPanel.java new file mode 100644 index 0000000000000000000000000000000000000000..c571ffa447705b0673c8c37293f95fd5d37ca416 --- /dev/null +++ b/KvizObserver/src/main/java/MultipleChoiceQuestionPanel.java @@ -0,0 +1,213 @@ +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import javafx.scene.control.ProgressBar; +import model.PlayerAnswer; + +import javax.imageio.ImageIO; +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; + +public class MultipleChoiceQuestionPanel extends JPanel implements FontResizable { + + private static final String LEFT_ARROW_PATH = "src/images/arrow_left.png"; + private static final String RIGHT_ARROW_PATH = "src/images/arrow_right.png"; + private JProgressBar progressBar; + + private static float fontSize = 60.0f; + private int arrowSize; + + private String question; + private String answer1; + private String answer2; + private String answer3; + private String answer4; + private String playerAnswersString; + private int correctAnswer; + private int timeOut; + + public MultipleChoiceQuestionPanel(String question, String answer1, String answer2, String answer3, String answer4, + String playerAnswersString, int correctAnswer, int timeOut) { + this.question = question; + this.answer1 = answer1; + this.answer2 = answer2; + this.answer3 = answer3; + this.answer4 = answer4; + this.playerAnswersString = playerAnswersString; + this.correctAnswer = correctAnswer; + this.timeOut = timeOut; + + initialize(); + + if (timeOut > -1) { + ProgressBarThread pbt = new ProgressBarThread(); + Thread progressBarThread = new Thread(pbt); + progressBarThread.start(); + } + + } + + class ProgressBarThread implements Runnable { + + private long startTime; + + public ProgressBarThread() { + startTime = new Date().getTime(); + } + + @Override + public void run() { + long currentTime = new Date().getTime(); + while (currentTime - startTime < timeOut) { + + progressBar.setValue((int) (timeOut - currentTime + startTime)); + try { + Thread.sleep(20); + } catch (InterruptedException e) { + e.printStackTrace(); + } + currentTime = new Date().getTime(); + } + } + } + + private void initialize() { + this.removeAll(); + this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); + if (timeOut > -1) { + progressBar = new JProgressBar(0, timeOut); + this.add(progressBar); + } + JTextArea questionTextArea = new JTextArea(question); + questionTextArea.setBackground(this.getBackground()); + questionTextArea.setBorder(new EmptyBorder(20,20,20,20)); + questionTextArea.setEditable(false); + questionTextArea.setWrapStyleWord(true); + questionTextArea.setLineWrap(true); + Font bigFont = questionTextArea.getFont().deriveFont(fontSize); + questionTextArea.setFont(bigFont); + questionTextArea.setAlignmentX(Component.LEFT_ALIGNMENT); + this.add(questionTextArea); + JLabel[] answerLabels = new JLabel[4]; + JLabel[] leftArrows = new JLabel[4]; + JLabel[] rightArrows = new JLabel[4]; + String[] answers = new String[4]; + answers[0] = answer1; + answers[1] = answer2; + answers[2] = answer3; + answers[3] = answer4; + arrowSize = (int) (192 * fontSize / 200); + for (int i=0;i<4;i++) { + JPanel answerPanel = new JPanel(new FlowLayout()); + answerPanel.setAlignmentX(Component.LEFT_ALIGNMENT); + leftArrows[i] = new JLabel(); + leftArrows[i].setPreferredSize(new Dimension(arrowSize, arrowSize)); + answerLabels[i] = new JLabel(answers[i]); + answerLabels[i].setFont(bigFont); + answerLabels[i].setMinimumSize(new Dimension((int) (10 * fontSize), (int) (1.1 * fontSize))); + answerLabels[i].setPreferredSize(new Dimension((int) (10 * fontSize), (int) (1.1 * fontSize))); + answerLabels[i].setMaximumSize(new Dimension((int) (10 * fontSize), (int) (1.1 * fontSize))); + answerLabels[i].setHorizontalAlignment(JLabel.CENTER); + rightArrows[i] = new JLabel(); + rightArrows[i].setPreferredSize(new Dimension(arrowSize, arrowSize)); + answerPanel.add(leftArrows[i]); + answerPanel.add(answerLabels[i]); + answerPanel.add(rightArrows[i]); + this.add(answerPanel); + } + + Gson gson = new Gson(); + Type collectionType = new TypeToken<Collection<PlayerAnswer>>(){}.getType(); + Collection<PlayerAnswer> playerAnswerCollection = gson.fromJson(playerAnswersString, collectionType); + List<PlayerAnswer> playerAnswers = new ArrayList<PlayerAnswer>(playerAnswerCollection); + + if (correctAnswer > -1) { + answerLabels[correctAnswer].setBackground(Color.GREEN); + answerLabels[correctAnswer].setOpaque(true); + } + + if (playerAnswers.size() <= 2) { + int firstPlayerAnswer = playerAnswers.get(0).getValue(); + if (firstPlayerAnswer > -1) { + try { + BufferedImage img = null; + img = ImageIO.read(new File(RIGHT_ARROW_PATH)); + img = resizeImage(img, arrowSize, arrowSize); + ImageIcon icon = new ImageIcon(img); + leftArrows[firstPlayerAnswer].setIcon(icon); + } catch (IOException e) { + e.printStackTrace(); + } + if (firstPlayerAnswer != correctAnswer) { + answerLabels[firstPlayerAnswer].setBackground(Color.RED); + answerLabels[firstPlayerAnswer].setOpaque(true); + } + } + if (playerAnswers.size() == 2) { + int secondPlayerAnswer = playerAnswers.get(1).getValue(); + if (secondPlayerAnswer > -1) { + try { + BufferedImage img = null; + img = ImageIO.read(new File(LEFT_ARROW_PATH)); + img = resizeImage(img, arrowSize, arrowSize); + ImageIcon icon = new ImageIcon(img); + rightArrows[secondPlayerAnswer].setIcon(icon); + } catch (IOException e) { + e.printStackTrace(); + } + if (firstPlayerAnswer != correctAnswer) { + answerLabels[secondPlayerAnswer].setBackground(Color.RED); + answerLabels[secondPlayerAnswer].setOpaque(true); + } + } + } + } + + if (playerAnswers.size() > 2) { + for (int i=0;i<playerAnswers.size();i++) { + JLabel playerAnswerLabel = new JLabel(); + playerAnswerLabel.setFont(bigFont); + playerAnswerLabel.setMinimumSize(new Dimension((int) (10 * fontSize), (int) (1.1 * fontSize))); + playerAnswerLabel.setPreferredSize(new Dimension((int) (10 * fontSize), (int) (1.1 * fontSize))); + playerAnswerLabel.setMaximumSize(new Dimension((int) (10 * fontSize), (int) (1.1 * fontSize))); + playerAnswerLabel.setHorizontalAlignment(JLabel.CENTER); + + PlayerAnswer answer = playerAnswers.get(i); + if (answer.getValue() > -1) { + playerAnswerLabel.setText(answer.getPlayerName() + ": " + + answers[answer.getValue()]); + this.add(playerAnswerLabel); + } + } + } + } + + public static BufferedImage resizeImage(BufferedImage img, int width, int height) { + Image tmp = img.getScaledInstance(width, height, Image.SCALE_SMOOTH); + BufferedImage dimg = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + + Graphics2D g2d = dimg.createGraphics(); + g2d.drawImage(tmp, 0, 0, null); + g2d.dispose(); + + return dimg; + } + + + @Override + public void setFontSize(int fontSize) { + this.fontSize = (float) fontSize; + initialize(); + this.revalidate(); + this.repaint(); + } + +} diff --git a/KvizObserver/src/main/java/model/PlayerAnswer.java b/KvizObserver/src/main/java/model/PlayerAnswer.java new file mode 100644 index 0000000000000000000000000000000000000000..3706cf85f9030656e0c76376e38f2c2279fd8699 --- /dev/null +++ b/KvizObserver/src/main/java/model/PlayerAnswer.java @@ -0,0 +1,44 @@ +package model; + +public class PlayerAnswer { + private String playerName; + private int value = -1; + private double time = 0; + private boolean winner = false; + + public PlayerAnswer(String playerName) { + this.playerName = playerName; + } + + public String getPlayerName() { + return playerName; + } + + public void setPlayerName(String playerName) { + this.playerName = playerName; + } + + public int getValue() { + return value; + } + + public void setValue(int value) { + this.value = value; + } + + public double getTime() { + return time; + } + + public void setTime(double time) { + this.time = time; + } + + public boolean isWinner() { + return winner; + } + + public void setWinner(boolean winner) { + this.winner = winner; + } +} diff --git a/KvizServer/app/src/main/java/onlab/kvizserver/GameActivity.java b/KvizServer/app/src/main/java/onlab/kvizserver/GameActivity.java index 3512480c97b08af3b07604879f7eade14ded52e4..b459567d79090d60fe78bc0f7cbe0ec200fbe831 100644 --- a/KvizServer/app/src/main/java/onlab/kvizserver/GameActivity.java +++ b/KvizServer/app/src/main/java/onlab/kvizserver/GameActivity.java @@ -81,6 +81,11 @@ public class GameActivity extends AppCompatActivity implements GameControlFragme new OutputStreamWriter(ClientHolder.get(i).getClientsocket().getOutputStream())), true); outputs.add(output); } + for (int i=0;i<ObserverHolder.size();i++) { + PrintWriter output = new PrintWriter(new BufferedWriter( + new OutputStreamWriter(ObserverHolder.get(i).getClientsocket().getOutputStream())), true); + outputs.add(output); + } } catch (IOException e) { e.printStackTrace(); } @@ -204,7 +209,7 @@ public class GameActivity extends AppCompatActivity implements GameControlFragme } String playerAnswersString = gson.toJson(playerAnswers); - for (int i=0;i<outputs.size();i++) { + for (int i=0;i<numberOfPlayers;i++) { gameControlFragment.displayAnswerOfPlayer(i,"The answer of " + playerAnswers[i].getPlayerName() + ": "); if (question.getType() == Question.MULTIPLE_CHOICE) { String message = "questionMC##" + questionString + "##" + answers.get(0) + "##" + answers.get(1) @@ -216,6 +221,14 @@ public class GameActivity extends AppCompatActivity implements GameControlFragme new SendMessageToClientTask().execute(Integer.toString(i), message); } } + for (int i=numberOfPlayers;i<outputs.size();i++) { //observerek + if (question.getType() == Question.MULTIPLE_CHOICE) { + String message = "questionMC##" + questionString + "##" + answers.get(0) + "##" + answers.get(1) + + "##" + answers.get(2) + "##" + answers.get(3) + "##" + playerAnswersString + + "##-1##" + Integer.toString(timeLimit); + new SendMessageToClientTask().execute(Integer.toString(i), message); + } + } /* gameControlFragment.displayEndOfGame(); @@ -228,7 +241,7 @@ public class GameActivity extends AppCompatActivity implements GameControlFragme @Override public void correctAnswerButtonClicked() { gameControlFragment.setCorrectAnswerButtonEnabled(false); - for (int i=0;i<outputs.size();i++) { + for (int i=0;i<numberOfPlayers;i++) { if (answers.size() == 4) { String playerAnswersString = gson.toJson(playerAnswers); String message = "answerMC##" + questionString + "##" + answers.get(0) + "##" + answers.get(1) @@ -243,6 +256,16 @@ public class GameActivity extends AppCompatActivity implements GameControlFragme new SendMessageToClientTask().execute(Integer.toString(i), message); } } + for (int i=numberOfPlayers;i<outputs.size();i++) { //observerek + if (question.getType() == Question.MULTIPLE_CHOICE) { + String playerAnswersString = gson.toJson(playerAnswers); + String message = "answerMC##" + questionString + "##" + answers.get(0) + "##" + answers.get(1) + + "##" + answers.get(2) + "##" + answers.get(3) + "##" + playerAnswersString + + "##" + Integer.toString(correctAnswer) + "##-1"; + new SendMessageToClientTask().execute(Integer.toString(i), message); + } + } + } @Override diff --git a/KvizServer/app/src/main/java/onlab/kvizserver/LobbyActivity.java b/KvizServer/app/src/main/java/onlab/kvizserver/LobbyActivity.java index 533dd0f29973dce8f3a1e6b8a8540f84d0436fb1..3f9cda7145ab6c985b0f6522b7bcb09ffb7188d0 100644 --- a/KvizServer/app/src/main/java/onlab/kvizserver/LobbyActivity.java +++ b/KvizServer/app/src/main/java/onlab/kvizserver/LobbyActivity.java @@ -112,6 +112,7 @@ public class LobbyActivity extends AppCompatActivity { commThread.interrupt(); } ClientHolder.addAll(clients); + ObserverHolder.addAll(observers); Intent intent; if (gameMode.equals("normal")) { intent = new Intent(getApplicationContext(), GameActivity.class); diff --git a/KvizServer/app/src/main/java/onlab/kvizserver/ObserverHolder.java b/KvizServer/app/src/main/java/onlab/kvizserver/ObserverHolder.java new file mode 100644 index 0000000000000000000000000000000000000000..b50edec2fc1667d5dfb71f8f1227aff8d533512e --- /dev/null +++ b/KvizServer/app/src/main/java/onlab/kvizserver/ObserverHolder.java @@ -0,0 +1,30 @@ +package onlab.kvizserver; + +import java.util.ArrayList; +import java.util.List; + +import onlab.kvizserver.model.ClientModel; + +public class ObserverHolder { + private static List<ClientModel> observers = new ArrayList<>(); + + public static void add(ClientModel clientModel) { + observers.add(clientModel); + } + + public static void remove(ClientModel clientModel) { + observers.remove(clientModel); + } + + public static void addAll(List<ClientModel> observers) { + ObserverHolder.observers.addAll(observers); + } + + public static ClientModel get(int index) { + return observers.get(index); + } + + public static int size() { + return observers.size(); + } +}