ConfigurableUnit.java

package eu.javaexperience.config;

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

import eu.javaexperience.collection.CollectionTools;
import eu.javaexperience.exceptions.UnimplementedCaseException;
import eu.javaexperience.semantic.designedfor.Immutable;
import eu.javaexperience.semantic.references.MayNull;
import eu.javaexperience.verify.WeightedValidationReportEntry;

/**
 * A unit that can be configured by C kind of object and
 * provides U kind of object 
 * 
 * provides WeightedValidationReportEntry<V> type of validation entries.
 * */
public abstract class ConfigurableUnit<C, U, V>
{
	public enum ConfigurableUnitState
	{
		UNCONFIGURED_NEW_UNIT,
		VALID_CONFIGURATION_SET,
		INITIALIZED,
		
		BEFORE_UPGRADE,//aka need reinitalize
		
		DESTROYED
	}
	
	public ConfigurableUnit(String name)
	{
		this.name = name;
	}
	
	protected ConfigurableUnitState state = ConfigurableUnitState.UNCONFIGURED_NEW_UNIT;
	
	protected String name;
	
	protected C currentConfig;
	protected C desiredConfig;
	
	protected U unit;
	
	public synchronized ConfigurableUnitState getState()
	{
		return state;
	}
	
	public synchronized void setConfig(@Immutable C cfg)
	{
		checkStateAndSetValidatedConfig(cfg);
	}
	
	protected void checkStateAndSetValidatedConfig(C cfg)
	{
		if(state == ConfigurableUnitState.DESTROYED)
		{
			throw new RuntimeException("Can't set new configuration to "+this.toString()+" because it's already destroyed");
		}
		
		List<WeightedValidationReportEntry<V>> v = new ArrayList<>(); 
		if(!validateConfig(cfg, v))
		{
			throw new RuntimeException("New Configuration: `"+cfg+"` for: `"+toString()+"` is invalid. ValidationEntries: "+CollectionTools.toStringMultiline(v));
		}
		
		desiredConfig = cfg;
		if(state == ConfigurableUnitState.UNCONFIGURED_NEW_UNIT)
		{
			state = ConfigurableUnitState.VALID_CONFIGURATION_SET;
		}
		else
		{
			state = ConfigurableUnitState.BEFORE_UPGRADE;
		}
	}
	
	public abstract boolean validateConfig(C config, Collection<WeightedValidationReportEntry<V>> validationRemarks);
	
	public void assertHasConfig()
	{
		if(state != ConfigurableUnitState.VALID_CONFIGURATION_SET && state != ConfigurableUnitState.BEFORE_UPGRADE)
		{
			throw new RuntimeException(toString()+" has no valid configuration, can not be initalized.");
		}
	}
	
	public C getCurrentConfig()
	{
		return currentConfig;
	}
	
	public C getDesiredConfigfuration()
	{
		return desiredConfig;
	}
	
	public synchronized void initialize()
	{
		assertNotDestroyed();
		assertHasConfig();
		unit = internalInitalize(desiredConfig, unit);
		currentConfig = desiredConfig;
		desiredConfig = null;
		state = ConfigurableUnitState.INITIALIZED;
	}
	
	public synchronized void rollback()
	{
		if(ConfigurableUnitState.INITIALIZED == state)
		{
			throw new RuntimeException("Can't rollback unit: already initalized.");
		}
		
		assertNotDestroyed();
		assertHasConfig();
		desiredConfig = null;
		switch(state)
		{
		case UNCONFIGURED_NEW_UNIT:
		case DESTROYED:
		case INITIALIZED:
			throw new RuntimeException("Bad synchronization, ConfigurableUnit is in an illegal state `"+state+"` after state check in a synchronized block");
			
		case BEFORE_UPGRADE:
			state = ConfigurableUnitState.UNCONFIGURED_NEW_UNIT;
			break;
		
		case VALID_CONFIGURATION_SET:
			state = ConfigurableUnitState.INITIALIZED;
			break;
			
		default: throw new UnimplementedCaseException(state);
		}
	}
	
	protected abstract U internalInitalize(C cfg, @MayNull U prevUnit);
	
	public synchronized void destroy()
	{
		assertNotDestroyed();
		state = ConfigurableUnitState.DESTROYED;
		internalDestroy(unit);
		unit = null;
	}
	
	protected abstract void internalDestroy(U unit);
	
	public void assetUnitReady()
	{
		assertNotDestroyed();
		assertUnitInitalized();
	}
	
	public void assertNotDestroyed()
	{
		if(state == ConfigurableUnitState.DESTROYED)
		{
			throw new IllegalStateException(toString()+" already destroyed");
		}
	}
	
	public void assertUnitInitalized()
	{
		if(state == ConfigurableUnitState.VALID_CONFIGURATION_SET || state == ConfigurableUnitState.UNCONFIGURED_NEW_UNIT)
		{
			throw new IllegalStateException(toString()+" not yet initalized");
		}
	}
	
	public synchronized U getUnit()
	{
		assetUnitReady();
		return unit;
	}
	
	@Override
	public String toString()
	{
		return "ConfigurableUnit("+name+")";
	}

	public void setName(String name)
	{
		this.name = name;
	}
	
	public String getName()
	{
		return name;
	}
	
	public static String formatName(Class cls, String userDefinedName)
	{
		return cls.getSimpleName()+"["+userDefinedName+"]";
	}
}