Copying objects of unknown class in Java requires using reflection. It's hideous.
Tags: java
Published: 2020-02-20 16:54:17

In my 'Software Design and Architecture' course, we are currently learning design patterns such as the *Command* pattern. From Wikipedia: 

> ... a command object is used to encapsulate all information needed to perform an action or trigger an event at a later time. This information includes the method name, the object that owns the method and values for the method parameters.

Here's a [UML diagram](https://www.dofactory.com/images/diagrams/net/command.gif). The purpose is to provide flexibility so that a receiver can handle many different commands without needing to be modified. An example given for our assignment is a 'universal' remote control for various household appliances: how does the manufacturer of the remote control integrate with a variety of appliances from different manufacturers?

The solution is to provide an interface which appliance manufacturers can adapt their software to and to encapsulate the commands as objects that are passed to the client. Our project assignment involved implementing a demo with an "undo" button capable of undo-ing multiple actions in a row. Here's where the headache with Java comes in:

Naturally, I created a Stack of 'Commands' and upon each call to *commandName.execute()*, the *commandName* is pushed to the stack and if the user hits 'undo', the command is popped in order to call *commandName.undo()*:

	public void onButtonWasPushed(int slot) {
		onCommands[slot].execute();
		undoCommands.push(onCommands[slot]);
	}

Here is the test driver provided by our instructor:

		RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
 
		CeilingFan ceilingFan = new CeilingFan("Living Room");
   
		CeilingFanLowCommand ceilingFanLow = 
				new CeilingFanLowCommand(ceilingFan);
		CeilingFanMediumCommand ceilingFanMedium = 
				new CeilingFanMediumCommand(ceilingFan);
		CeilingFanHighCommand ceilingFanHigh = 
				new CeilingFanHighCommand(ceilingFan);
		CeilingFanOffCommand ceilingFanOff = 
				new CeilingFanOffCommand(ceilingFan);
  
		remoteControl.setCommand(0, ceilingFanLow, ceilingFanOff);
		remoteControl.setCommand(1, ceilingFanMedium, ceilingFanOff);
		remoteControl.setCommand(2, ceilingFanHigh, ceilingFanOff);
   
		System.out.println("4 on buttons");
		remoteControl.onButtonWasPushed(1); //ceiling fan goes to medium
		remoteControl.onButtonWasPushed(0); // ->low
		remoteControl.onButtonWasPushed(2); // ->high
		remoteControl.onButtonWasPushed(1); // ->medium
		// undo stack has 
		//   ceilingFanMedium at top
		//   ceilingFanHigh  
		//   ceilingFanLow 
		//   ceilingFanMedium at bottom
		
		System.out.println("undo command");
		remoteControl.undoButtonWasPushed(); // go back to high
		System.out.println("redo command");
		remoteControl.redoButtonWasPushed(); // return to medium
		
		// undo stack has 
		//   ceilingFanMedium at top
		//   ceilingFanHigh  
		//   ceilingFanLow 
		//   ceilingFanMedium at bottom		
		
		System.out.println("undo 4 commands");
		remoteControl.undoButtonWasPushed(); // go back to high
		remoteControl.undoButtonWasPushed(); // go back to low
		remoteControl.undoButtonWasPushed(); // go back to medium
		remoteControl.undoButtonWasPushed(); // SHOULD go back to off but does not in my implementation

Now of course this did not have the output I had expected because in the driver application code, we are passing *the same command object instance* to the stack each time we select a particular setting (instead of a new instance of that command), and the Command knows how to *undo()* itself based on a non-public field where it stores the previous command:

	CeilingFan ceilingFan;
	int prevSpeed;
  
	public CeilingFanMediumCommand(CeilingFan ceilingFan) {
		this.ceilingFan = ceilingFan;
	}
 
	public void execute() {
		prevSpeed = ceilingFan.getSpeed();
		ceilingFan.medium();
	}
 
	public void undo() {
		if (prevSpeed == CeilingFan.HIGH) {
			ceilingFan.high();
		} else if (prevSpeed == CeilingFan.MEDIUM) {
			ceilingFan.medium();
		} else if (prevSpeed == CeilingFan.LOW) {
			ceilingFan.low();
		} else if (prevSpeed == CeilingFan.OFF) {
			ceilingFan.off();
		}
	}
So the result of the final call to remotecontrol.undoButtonWasPushed() in the driver is to go back to 'high' setting because the reference to the Command at the *bottom* of the stack points to the same instance as the Command referenced at the *top* of the stack! That command is trying to 'undo()' something that has already been undone.

I realize I need a new instance each time *commandName.execute()* is called, so I look up Java's clone() method, where the object being cloned must implement it's own deep copy. Unfortunately, this requires modifying the vendor code, which the remote manufacturer wouldn't have access to. I could try a [copy constructor](https://dzone.com/articles/java-cloning-copy-constructor-vs-cloning) instead but that again assumes at least knowledge of the vendor code. If I want to keep my code decoupled or I don't know the details of the vendor implementation, then I can't rely on creating a new object and copying each field directly.

What does that leave us with? Well, I realize [my only option seems to be using Java's reflection API](https://stackoverflow.com/a/26545275/12929178) and ... I don't like it. I'm obviously a beginner so share your thoughts, is there a better way to copy unknown objects in Java or is reflection not so convoluted after all?

> There is no general mechanism for this in Java. For a class to allow copying objects, it should implement a copying mechanism (for example Cloneable).

> In principle, I suppose it would be possible to copy objects using reflection, picking one member at a time and building a copy, but it will be difficult to make it work. Note that it means you have to access private members too. And even if you succeed, there is no guarantee that it will work as you expect for classes that you don't control…