/*
 * ObserverDemo.java:
 *
 * A simple example of the Observer pattern.  We create a model that records a
 * temperature, and three views that display the temperature in degrees Celsius,
 * Kelvin, and Fahrenheit.  A change made to the temperature through any of the
 * views is immediately reflected in all three views.  (The change takes effect
 * when the user presses return; if the cursor leaves the field before the user
 * presses return, the old value is restored.)
 *
 * Copyright (c) 2001 - Russell C. Bjork
 *
 */
 
import java.util.Observer;
import java.util.Observable;
import java.awt.*;
import java.awt.event.*;

// THE OBSERVABLE

/** Store a temperature and make it available for access/update by GUI 
 */
class Temperature extends Observable
{
	private double value;		// recorded internally in Celsius
	
	/** Constructor - initialize to 0 Celsius */
	
	public Temperature()
	{
		value = 0;
	}
	
	/** Accessors for temperature
	 *
	 *	@return temperature (in celsius)
	 */
	public double get()
	{
		return value;
	}
	
	/** Mutator - set temperature to new value
	 *
	 *	@param celsius new temperature (in celsius)
	 */
	public void set(double celsius)
	{
		value = celsius;
		setChanged();
		notifyObservers();
	}
}

// THE OBSERVER - ABSTRACT BASE CLASS, AND THREE CONCRETE SUBCLASSES

/** Display a GUI and allow user to change it.  This is an abstract class -
 *  concrete subclasses must specify conversion to/from celsius
 */
abstract class TemperatureDisplay extends Frame implements Observer
{
	private Label label;
	private TextField data;
	private Temperature temperature;
	
	/** Constructor
	 *
	 *	@param description descriptive label to show
	 *  @param temperature Temperature object whose value is to be shown
	 */
	 
	protected TemperatureDisplay(String description, Temperature temperature)
	{
		setFont(new Font("Monospaced", Font.PLAIN, 24));
		label = new Label(description, Label.CENTER);
		add(label, BorderLayout.NORTH);
		data = new TextField(20);
		add(data, BorderLayout.SOUTH);
		
		data.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event)
			{ changeTemperature(); }
		  });

		data.addFocusListener(new FocusAdapter() {
			public void focusLost(FocusEvent e)
			{ changeTemperature(); }
		  });
		  
		this.temperature = temperature;
		temperature.addObserver(this);
		
		// Set initial value
		
		update(temperature, null);
		  
		pack();
	}
	
	/** Convert value in system used by this object to celsius
	 *
	 *	@param value value to convert
	 *	@return equivalent celsius value
	 */
	protected abstract double toCelsius(double value);
	
	/** Convert a celsius value to system used by this object
	 *
	 *	@param celsius celsius value to convert
	 *	@return equivalent value in system used by this object
	 */
	protected abstract double fromCelsius(double celsius);
	
	/** Method required by Observer interface - update display when model is
	 *  changed
	 *
	 *	@param o the Observable object that changed
	 *	@param arg the argument to the notifyObservers() method of object
	 *
	 */
	public void update(Observable o, Object arg)
	{
		double newValue = ((Temperature) o).get();
		data.setText("" + fromCelsius(newValue));
	}
	
	/** Change the temperature recorded by the model as a result of a change
	 *	made by the user to the value shown in this window.
	 */
	private void changeTemperature()
	{
		try
		{
			double newValue = Double.valueOf(data.getText()).doubleValue();
			temperature.set(toCelsius(newValue));
		}
		catch(NumberFormatException exception)
		{
			data.setText("Malformed number");
			data.selectAll();
		}
	}
}
	
/** Concrete implementation of TemperatureDisplay for Celsius system */
class CelsiusDisplay extends TemperatureDisplay
{
	public CelsiusDisplay(Temperature temperature)
	{
		super("Celsius", temperature);
	}
	
	protected double toCelsius(double value)
	{
		return value;
	}
	
	protected double fromCelsius(double celsius)
	{
		return celsius;
	}
}

/** Concrete implementation of TemperatureDisplay for Kelvin system */
class KelvinDisplay extends TemperatureDisplay
{
	public KelvinDisplay(Temperature temperature)
	{
		super("Kelvin", temperature);
	}
	
	protected double toCelsius(double value)
	{
		return value - 273;
	}
	
	protected double fromCelsius(double celsius)
	{
		return celsius + 273;
	}
}

/** Concrete implementation of TemperatureDisplay for Fahrenheit system */
class FahrenheitDisplay extends TemperatureDisplay
{
	public FahrenheitDisplay(Temperature temperature)
	{
		super("Fahrenheit", temperature);
	}
	
	protected double toCelsius(double value)
	{
		return (value - 32) * 5 / 9;
	}
	
	protected double fromCelsius(double celsius)
	{
		return 32 + 1.8 * celsius;
	}
}

// MAIN PROGRAM CLASS FOR DEMONSTRATION

class Demo
{
	public static void main(String [] args)
	{
		Temperature model = new Temperature();
		CelsiusDisplay celsius = new CelsiusDisplay(model);
		KelvinDisplay kelvin = new KelvinDisplay(model);
		FahrenheitDisplay fahrenheit = new FahrenheitDisplay(model);
		celsius.setVisible(true);
		celsius.setLocation(100, 100);
		kelvin.setVisible(true);
		kelvin.setLocation(200, 200);
		fahrenheit.setVisible(true);
		fahrenheit.setLocation(300, 300);
	}
}
		
		

