Serialisierungs- bzw. Clone-Problem

HansWM

Grünschnabel
Erstmal schönen Dank für alle vorherigen Hilfen.
Nun ein neues Problem.
Die nachfolgende Klasse soll sich um die Verwaltung des Boards in einem kleinen Brettspiel kümmern. Ich möchte auch eine Zugrücknahme implementieren. Dazu muß ich alle Spielstellungen zwischenspeichern. Das soll eine ArrayList machen. Das eigentliche Spielbrett besteht aus der Klasse TheInnerBoard die ein Array enthält. Jedes Feld des Arrays enthält eine Referenz auf den entsprechenden Spieler oder null falls für das Spielfeld kein Spielstein gesetzt wurde. Nun möchte ich diese Spielbretter (TheInnerBoard) in der ArrayList speichern. Das Problem sind die Referenzen auf die Spieler. Ich habe die Klasse TheInnerBoard und Fields als Serializable gekennzeichnet, erhalte bei der Ausführung die Meldung das immer noch weitere Klassen als Serializable gekennzeichnet werden müssen. Das nimmt kein Ende. Ich habe es auch schon mit Klonen probiert (Object serialClone). Das funktioniert aber auch nicht.

Code:
import java.util.ArrayList;
import java.util.List;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.lang.ClassNotFoundException;
import java.io.Serializable;
import java.io.NotSerializableException;

// In OthelloInterface stehen nur ein paar Konstanten enum usw.	 
public class OthelloBoard implements OthelloInterface
{  
	// Aus Goto Java 2
    	public static Object serialClone (Object o) throws IOException, ClassNotFoundException
    	{
    		ByteArrayOutputStream out = new ByteArrayOutputStream ();
    		ObjectOutputStream os = new ObjectOutputStream (out);
    		os.writeObject (o);
    		os.flush ();;
    		ByteArrayInputStream in = new ByteArrayInputStream (out.toByteArray ());
    		ObjectInputStream is = new ObjectInputStream (in);
    		Object ret = is.readObject();
    		is.close();
    		os.close();
    		return ret;
    	}
     
     // Reference auf das Game
  	private OthelloGame thisGame;
  	// Position in der ArrayList
  	private int Pointer;

 	// Die ArrayList mit den Boards
	  	public List <TheInnerBoard> History = new ArrayList <TheInnerBoard>();
 
        	 
  		public void clearHistory ()
  		{
  			History.clear ();
  			Pointer = 0;
  		}
  	
  	    // Reference auf den Spieler bzw. null (Kein Spielstein) 
  		private class Field implements Serializable
  		{
   			OthelloPlayer Player;                
  		};

        public class TheInnerBoard implements Serializable
        {
        	Field[][] Fields;
        };	
        
        TheInnerBoard TheBoard;
        
        // Kopiert die Inhalte aber nicht die Referenzen auf die Spieler
        // Inhalte kopieren und anhand der Ordinalität die Referenzen wieder herstellen ?
        private TheInnerBoard copy (TheInnerBoard xyz)
        {
        	TheInnerBoard TheBoard = new TheInnerBoard ();
        	// Hier knallts beim Auslesen bzw. zurückkopieren aus ArrayList
        	System.out.println ("Size = "+xyz.Fields.length);
        	int size = xyz.Fields.length;
        	TheBoard.Fields = new Field[size][size];
        	
        	for (int row = 0; row < size; ++row)
        	{
        		for (int column = 0; column < size; ++column)
        		{
        			TheBoard.Fields[row][column] = new Field();
        			try
        			{
        				System.out.println ("Color = "+xyz.Fields[row][column].Player.color);
       	  				System.out.println ("Type = "+xyz.Fields[row][column].Player.type);
       	  				System.out.println ("Name = "+xyz.Fields[row][column].Player.name);
       	  				System.out.println ("Ord = "+xyz.Fields[row][column].Player.ord);
       	  		
       	  			TheBoard.Fields[row][column].Player.color = xyz.Fields[row][column].Player.color;
                        TheBoard.Fields[row][column].Player.type = xyz.Fields[row][column].Player.type;
       	  				TheBoard.Fields[row][column].Player.name = xyz.Fields[row][column].Player.name;
       	  				TheBoard.Fields[row][column].Player.ord = xyz.Fields[row][column].Player.ord;
       	  	 		    	  			
       	  			} 
       	  			catch (NullPointerException e)
       	  	        {
       	  	        	// System.out.println ("Player = null");
       	  	            TheBoard.Fields[row][column].Player = null;	
       	  	        };
        		}
        	}
        	return TheBoard;
        }
     
    	// Arrayindex von 0..n      
  		public OthelloBoard (OthelloGame game, int size)
  		{
  			// Referenz auf das Spiel speichern
  			thisGame = game;
  			// An den Anfang der ArrayList
        	Pointer = 0;
            // Eine neue Instanz der Klasse des Arrays anlegen     
  			TheBoard = new TheInnerBoard ();
  			// Eine neue Instanz des Arrays anlegen
  			TheBoard.Fields = new Field[size][size];
  		    // Die Elemente des Arrays anlegen
  			for (int row = 0; row < size; row++)
   			{
    			for (int column = 0; column < size; column ++)
       			{
       	  			TheBoard.Fields[row][column] = new Field();
       	  			// Mit null initialisieren
       	  			TheBoard.Fields[row][column].Player = null;
				}
    		}
    		// Das Spielfeld initialisieren
    		initBoard (size);	
		}
    	
    	// Die Klasse des Arrays serialisieren
    	public Object cloneThis (TheInnerBoard xyz)
    	{
    		Object copy = null;
    		try
    		{	
    	    	copy = serialClone (xyz);
    		}
    		catch (IOException e)
    		{
    			System.err.println (e.toString());
    		}	
    		catch (ClassNotFoundException e)
    		{
    			System.err.println (e.toString());
    		}
         
    		return (copy);
    	}
    
    	// Ein neues Board in die ArrayList einfügen
  		public void addBoard (TheInnerBoard xxx)
  		{
  			System.out.println ("Pointer ="+Pointer);
 			System.out.println ("Steine = "+countStones());
 			
	    	TheInnerBoard Clone = (TheInnerBoard) cloneThis(xxx);
            // TheInnerBoard Clone = copy (xxx);
  			History.add(Pointer, Clone);
  			Pointer++;

  			System.out.println ("A new Board was inserted");
  		}	
  	 	
  		public void stepForward ()
  		{
  			if (Pointer < (History.size()-1))
  			{
  		  		System.out.println("One Step Forward");
            
  				Pointer++;
  		
  			 	TheBoard = History.get (Pointer);
  			 	// Loeschen und Neuzeichnen des neuen Boards
  				thisGame.getOthelloFrame().getPanel().eraseStones();
  			}	
  		}
  	
  		public void stepBackward ()
  		{
			if (Pointer>0)
			{
				System.out.println("One Step Backward");
				// TheInnerBoard xxx = new TheinnerBoard (Size);
				Pointer--;
				System.out.println("Pointer = "+Pointer);
				TheBoard = copy (History.get (Pointer));
		
				System.out.println ("Steine = "+countStones());

			}	
  		}
  		
    	private void initBoard (int size)
  		{		
  	    	System.out.println ("Board will be initialized");
  	    	// Holt sich die Player beim Game
  	    	TheBoard.Fields[size/2-1][size/2-1].Player = thisGame.getPlayer1();
  	    	TheBoard.Fields[size/2][size/2].Player = thisGame.getPlayer1();
  	    	TheBoard.Fields[size/2][size/2-1].Player = thisGame.getPlayer2();
  	    	TheBoard.Fields[size/2-1][size/2].Player = thisGame.getPlayer2();
  	
  		}
  	
  	    public OthelloPlayer getPlayer (int row, int column)
  	    {
  	    	return (TheBoard.Fields[row][column].Player);
  	    }	
  				 
  		public void setPlayer (int row, int column, OthelloPlayer player)
  		{
     		TheBoard.Fields[row][column].Player = player;
     		// Das neue Board in die ArrayList einfuegen
     		addBoard (TheBoard);
  		}
  	
  		public void removeStones ()
  		{
  			System.out.println ("All Stones will be removed");
  		
  			int row, column;
  		
  			for (row = 0; row < TheBoard.Fields.length; row++)
  			{
  				for (column = 0; column < TheBoard.Fields.length; column++)
  				{
  					TheBoard.Fields[row][column].Player = null;
  				}
  			}
  		
  			// Neuinitialisierung
	    	initBoard (TheBoard.Fields.length);
  		}	 	
  		
  		  			 
    	public int countStones (OthelloPlayer player)
    	{
    		int column, row, Stones = 0;
    	
    		for (column = 0; column < TheBoard.Fields.length; column++)
    		{
    			for (row = 0; row < TheBoard.Fields.length; row++)
    			{
    				if (TheBoard.Fields[column][row].Player == player)
    				{
    					Stones++;
    				}
    			}
    		}
    		return (Stones);
    	}
    	
    		  			 
    	public int countStones ()
    	{
    		int column, row, Stones = 0;
    	
    		for (column = 0; column < TheBoard.Fields.length; column++)
    		{
    			for (row = 0; row < TheBoard.Fields.length; row++)
    			{
    				if (TheBoard.Fields[column][row].Player != null)
    				{
    					Stones++;
    				}
    			}
    		}
    		return (Stones);
    	}

	
      
} // OthelloBoard
 
Erst mal ist es nicht schlecht alle Klassen zu suchen und serialisierbar zu machen. Die Zahl ist ja sicher endlich ;-)

Ich würde es aber mit dem Command-Pattern versuchen. Im Prinzip baust Du Dir aus jeder Benutzereingabe ein Command-Object, das eine execute und eine undo-Funktion hat. danach führts Du die exceute-Methode aus und speicherst das Command-Object in der Arraylist. Dasselbe macht Du mit den Rechner-Commands. Undo und redo ist kein dann kein Problem. Wenn Du das Spiel speichern willst, speicherts Du die Liste der Commands. Zum Laden startest Du Dein initiales Brett und führst die Commands nacheiander wieder aus. Damit müssen nur die Commands Serializable sein.


Hier ein kleiner Calculator als Beispiel. (Ist nur als Skizze zu verstehen)

Java:
package de.command;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Client {

	
	public Client() throws Exception {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		String eingabe = "";
		Calculator calc = new Calculator();
		CommandHistorie historie = new CommandHistorie();
		
		while ( ! eingabe.equalsIgnoreCase("Ende")) {
			System.out.print("#>");
			eingabe = in.readLine();
			
			if (eingabe.equalsIgnoreCase("undo") || eingabe.equalsIgnoreCase("redo")) {
				if (eingabe.equalsIgnoreCase("undo")) {
					historie.undo();
				}else {
					historie.redo();
				}
				
			} else {
			
				ICommand command = CommandFactory.createCommand(calc, eingabe);
				if ( command == null ) continue;
				
				if(! command.isQuery()) historie.add(command);
				command.execute();
			}
		}
		
	}
	
	
	public static void main(String[] args) throws Exception {
		new Client();

	}

}

Java:
package de.command;

import java.io.Serializable;

public class Calculator implements Serializable{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1;
	private double memory;
	
	public Calculator() {
		memory = 0;
	}
	
	public void add(double a) {
		memory += a;
	}

	public void sub(double a) {
		memory -= a;
	}
	
	public void list() {
		System.out.println(memory);
	}

	public double getMemory() {
		return memory;
	}

	public void setMemory(double memory) {
		this.memory = memory;
	}
	

}

Java:
package de.command;

import java.io.Serializable;

public interface ICommand extends Serializable{
	
	public void parse(Calculator c, String [] tokens);
	public void execute();
	public void undo();
	public boolean isQuery();

}

Java:
package de.command;

public class AddCommand implements ICommand {
	
	Calculator calc;
	double summand;

	public void execute() {
		calc.add(summand);

	}

	public boolean isQuery() {
		return false;
	}

	public void parse(Calculator c, String[] tokens) {
		calc = c;
		summand = Double.parseDouble(tokens[1]);

	}

	public void undo() {
		calc.sub(summand);

	}

}

Java:
package de.command;

public class PrintCommand implements ICommand {

	private Calculator calc;

	public void execute() {
		calc.list();

	}

	public boolean isQuery() {
		return true;
	}

	public void parse(Calculator c, String[] tokens) {
		 calc= c;

	}

	public void undo() {
		throw new UnsupportedOperationException("Häh");

	}

}

Java:
package de.command;

public class CommandFactory {

	public static ICommand createCommand(Calculator calc, String eingabe) {
		ICommand retval = null;
		String [] tokens = eingabe.split(" ");
		
		if(tokens[0].equalsIgnoreCase("add")) {
			retval = new AddCommand();
			retval.parse(calc, tokens);
		}
		
		if(tokens[0].equalsIgnoreCase("print")) {
			retval = new PrintCommand();
			retval.parse(calc, tokens);
		}
		
		return retval;
	}
}

Java:
package de.command;

import java.util.ArrayList;
import java.util.List;

public class CommandHistorie {
	
	private List commands = new ArrayList();
	private int index = -1;

	public boolean add(Object arg0) {
		index ++;
		return commands.add(arg0);
	}

	public void redo() {
		if (index >= commands.size()){
			System.out.println("Can't redo");
			return;
		}
		((ICommand) commands.get(++index)).execute();
		
	}
	
	public void undo() {
		if (index < 0){
			System.out.println("Can't undo");
			return;
		}
		((ICommand) commands.get(index--)).undo();
	}
	
	
	

}

Ich habe nur zwei Kommands eingebaut, add [zahl] und print. Aber für das Prinzip reichts. Die Command-Historie entspricht Deinem Array, ist aber noch nicht ganz ausprogrammiert. Sollte nur eine Anregung sein....


Gruss

EDIT: mein Calculator ist auch Serialisable. Das muss aber nicht unbedingt sein. Eine Referenz auf den Calculator könnte man sich auch bei Bedarf mit dem Singleton-Pattern (statischer Funktionsaufruf) holen.
 
Zuletzt bearbeitet:
Zurück