From d26bc47d9b0d293ae3d2bd38975f24310fc9be6e Mon Sep 17 00:00:00 2001
From: erik <erik@77661180-640e-0410-b3a8-9f9b13e6d0e0>
Date: Fri, 11 Apr 2008 20:22:46 +0000
Subject: [PATCH] Implemented optinality of interfaces.

---
 .../system/core/AbstractComponent.java        |   2 +-
 .../org/wamblee/system/core/Container.java    | 167 +++++++++++-------
 .../system/core/DefaultRequiredInterface.java |  16 ++
 .../system/core/RequiredInterface.java        |   8 +-
 .../org/wamblee/system/core/Application.java  |  12 +-
 .../wamblee/system/core/ContainerTest.java    |  46 +++++
 6 files changed, 183 insertions(+), 68 deletions(-)

diff --git a/system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java b/system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java
index 9bad650d..b202f285 100644
--- a/system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java
+++ b/system/general/src/main/java/org/wamblee/system/core/AbstractComponent.java
@@ -164,7 +164,7 @@ public abstract class AbstractComponent implements Component {
 
 	@Override
 	public String toString() {
-		return _name;
+		return getQualifiedName();
 	}
 
 }
diff --git a/system/general/src/main/java/org/wamblee/system/core/Container.java b/system/general/src/main/java/org/wamblee/system/core/Container.java
index 8436ebca..81dcb6a2 100644
--- a/system/general/src/main/java/org/wamblee/system/core/Container.java
+++ b/system/general/src/main/java/org/wamblee/system/core/Container.java
@@ -18,6 +18,7 @@ package org.wamblee.system.core;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 
 import org.apache.commons.logging.Log;
@@ -78,7 +79,7 @@ public class Container extends AbstractComponent {
 		for (Component component : aSystems) {
 			component.addContext(getQualifiedName());
 		}
-		validate(aRequired);
+		validate();
 	}
 
 	/**
@@ -87,7 +88,7 @@ public class Container extends AbstractComponent {
 	 * that cannot be provided. Also logs a warning in case of superfluous
 	 * requirements.
 	 */
-	private void validate(RequiredInterface[] aRequired) {
+	private void validate() {
 		List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
 		for (Component system : _systems) {
 			provided.addAll(Arrays.asList(system.getProvidedInterfaces()));
@@ -98,79 +99,105 @@ public class Container extends AbstractComponent {
 			required.addAll(Arrays.asList(system.getRequiredInterfaces()));
 		}
 
-		for (ProvidedInterface service : getProvidedInterfaces()) {
-			if (!(provided.contains(service))) {
-				throw new SystemAssemblyException(getName() + ": Service '"
-						+ service
-						+ "' is not provided by any of the subsystems");
-			}
-		}
+		validateProvidedInterfaces(provided);
 
-		for (RequiredInterface service : getRequiredInterfaces()) {
-			if (!(required.contains(service))) {
-				info("Service '"
-						+ service
-						+ "' indicated as required is not actually required by any of the subsystems");
-			}
+		validateRequiredInterfaces(required);
+
+		List<RequiredInterface> reallyRequired = validateRequiredProvidedMatch(
+				provided, required);
+		
+		String missingRequired = "";
+		for (RequiredInterface service : reallyRequired) {
+			missingRequired += service + "\n";
+		}
+		if (missingRequired.length() > 0) {
+			throw new SystemAssemblyException(getName()
+					+ ": missing required services\n" + missingRequired);
 		}
+	}
 
+	private List<RequiredInterface> validateRequiredProvidedMatch(
+			List<ProvidedInterface> provided, List<RequiredInterface> required) {
 		List<RequiredInterface> reallyRequired = new ArrayList<RequiredInterface>(
 				required);
 		// Compute all required interfaces that are not provided
+
 		for (ProvidedInterface service : provided) {
 			List<RequiredInterface> fulfilled = Arrays
 					.asList(filterRequiredServices(service, reallyRequired));
 			reallyRequired.removeAll(fulfilled);
 		}
+		// Now remove all optional interfaces from the list.
+		for (Iterator<RequiredInterface> i = 
+			reallyRequired.iterator(); i.hasNext(); ) { 
+			RequiredInterface req = i.next();
+			if ( req.isOptional() ) {
+				i.remove(); 
+			}
+		}
 		// Now the remaining interfaces should be covered by the required
 		// list.
-		reallyRequired.removeAll(Arrays.asList(aRequired));
+		reallyRequired.removeAll(Arrays.asList(getRequiredInterfaces()));
+		return reallyRequired;
+	}
 
-		String missingRequired = "";
-		for (RequiredInterface service : reallyRequired) {
-			missingRequired += service + "\n";
+	private void validateRequiredInterfaces(List<RequiredInterface> required) {
+		for (RequiredInterface service : getRequiredInterfaces()) {
+			// TODO required services by the subsystem could be 
+			//      subclasses or implementations of the requirements
+			//      of the contained systems. The code below assumes
+			//      an exact match. 
+			if (!(required.contains(service))) {
+				info("Service '"
+						+ service
+						+ "' indicated as required is not actually required by any of the subsystems");
+			}
+			// Check for the case that the externally required service
+			// is optional whereas the internally required service is 
+			// mandatory. 
+			if ( service.isOptional()) { 
+				for (RequiredInterface intf: required) { 
+					if ( intf.equals(service) && !intf.isOptional()) { 
+						// TODO indicate which subsystem this is. 
+						warn("Required service '" + service + "' indicated as optional is mandatory by one of its subsystems (" + getClients(intf) + ", " + intf + "), this can lead to problems when the system is started and the service is not there.");
+					}
+				}
+			}
 		}
-		if (missingRequired.length() > 0) {
-			throw new SystemAssemblyException(getName()
-					+ ": missing required services\n" + missingRequired);
+	}
+
+	private void validateProvidedInterfaces(List<ProvidedInterface> provided) {
+		for (ProvidedInterface service : getProvidedInterfaces()) {
+			// TODO provided interfaces by subsystems could be 
+			//      provide subclasses or implementations of the 
+			//      provided interfaces of the container.
+			//      The code below assumes an exact match. 
+			if (!(provided.contains(service))) {
+				throw new SystemAssemblyException(getName() + ": Service '"
+						+ service
+						+ "' is not provided by any of the subsystems");
+			}
 		}
 	}
 
 	@Override
 	protected void doStart() {
-		List<ProvidedInterface> provided = new ArrayList<ProvidedInterface>();
+		LOG.info("Starting '" + getQualifiedName() + "'");
+		List<ProvidedInterface> allProvided = new ArrayList<ProvidedInterface>();
 
 		// all interfaces from the required list of this container are
 		// provided to the components inside it.
 		RequiredInterface[] required = getRequiredInterfaces();
 		for (RequiredInterface intf : required) {
 			ProvidedInterface provider = intf.getProvider();
-			if (provider == null) {
-				throw new SystemAssemblyException(getQualifiedName()
-						+ ": required interface '" + intf + "' is not provided");
+			if (provider != null ) { 
+				allProvided.add(provider);
+			} else { 
+				if ( !intf.isOptional()) { 
+					throw new SystemAssemblyException(getQualifiedName()
+							+ ": required interface '" + intf + "' is not provided");		
+				}
 			}
-			provided.add(intf.getProvider());
-		}
-
-		startImpl();
-	}
-
-	/**
-	 * Starts the subsystems.
-	 * 
-	 * @param aRequiredServices
-	 *            Services that are available from other systems that have been
-	 *            started before.
-	 */
-	private void startImpl() {
-		LOG.info("Starting '" + getQualifiedName() + "'");
-		List<ProvidedInterface> allProvided = new ArrayList<ProvidedInterface>();
-
-		// Add the provides of all externally required interfaces to the list of
-		// available
-		// interfaces
-		for (RequiredInterface required : getRequiredInterfaces()) {
-			allProvided.add(required.getProvider());
 		}
 
 		List<Component> started = new ArrayList<Component>();
@@ -179,21 +206,13 @@ public class Container extends AbstractComponent {
 				// Check if all required services are already provided by
 				// earlier
 				// systems.
-				RequiredInterface[] required = system.getRequiredInterfaces();
 
-				for (RequiredInterface descriptor : required) {
+				for (RequiredInterface descriptor : system.getRequiredInterfaces()) {
 					ProvidedInterface[] filtered = filterProvidedServices(
 							descriptor, allProvided);
-
-					if (filtered.length == 0) {
-						throw new SystemAssemblyException(
-								"Service '"
-										+ descriptor
-										+ "' required by system '"
-										+ system
-										+ "' is not provided by systems that are started earlier");
-					}
-					if (filtered.length > 1) {
+					if ( filtered.length == 1 ) { 
+						descriptor.setProvider(filtered[0]);
+					} else if ( filtered.length > 1 ) { 
 						throw new SystemAssemblyException(
 								"Service '"
 										+ descriptor
@@ -201,8 +220,17 @@ public class Container extends AbstractComponent {
 										+ system
 										+ "' matches multiple services provided by other systems: "
 										+ Arrays.asList(filtered));
+					} else { 
+						// filtered.length == 0
+						if ( !descriptor.isOptional()) { 
+							throw new SystemAssemblyException(
+									"Service '"
+											+ descriptor
+											+ "' required by system '"
+											+ system
+											+ "' is not provided by systems that are started earlier");	
+						}
 					}
-					descriptor.setProvider(filtered[0]);
 				}
 
 				// Start the service.
@@ -241,7 +269,22 @@ public class Container extends AbstractComponent {
 	}
 
 	private void info(String aMsg) {
-		LOG.info(getName() + ": " + aMsg);
+		LOG.info(getQualifiedName() + ": " + aMsg);
+	}
+	
+	private void warn(String aMsg) {
+		LOG.warn(getQualifiedName() + ": " + aMsg);
 	}
 
+	private List<Component> getClients(RequiredInterface aRequirement) {
+		List<Component> clients = new ArrayList<Component>();
+		for (Component component: _systems) { 
+			for (RequiredInterface required: component.getRequiredInterfaces()) { 
+				if ( required.equals(aRequirement) && required.isOptional() == aRequirement.isOptional()) { 
+					clients.add(component);
+				}
+			}
+		}
+		return clients; 
+	}
 }
diff --git a/system/general/src/main/java/org/wamblee/system/core/DefaultRequiredInterface.java b/system/general/src/main/java/org/wamblee/system/core/DefaultRequiredInterface.java
index 32276a25..e028342e 100644
--- a/system/general/src/main/java/org/wamblee/system/core/DefaultRequiredInterface.java
+++ b/system/general/src/main/java/org/wamblee/system/core/DefaultRequiredInterface.java
@@ -20,6 +20,7 @@ import java.util.Arrays;
 public class DefaultRequiredInterface implements RequiredInterface {
 
 	private String _name;
+	private boolean _optional; 
 	private Class[] _required;
 	private ProvidedInterface _provider; 
 	
@@ -28,7 +29,17 @@ public class DefaultRequiredInterface implements RequiredInterface {
 	}
 
 	public DefaultRequiredInterface(String aName, Class[] aInterfaces) {
+		this(aName, aInterfaces, false); 
+	}
+	
+	public DefaultRequiredInterface(String aName, Class aInterface, boolean aIsOptional) {
+	    this(aName, new Class[] { aInterface }, aIsOptional );
+	}
+
+	
+	public DefaultRequiredInterface(String aName, Class[] aInterfaces, boolean aIsOptional) {
 		_name = aName; 
+		_optional = aIsOptional; 
 		_required = aInterfaces; 
 	}
 
@@ -37,6 +48,11 @@ public class DefaultRequiredInterface implements RequiredInterface {
 		return _name;
 	}
 	
+	@Override
+	public boolean isOptional() {
+		return _optional; 
+	}
+	
 	@Override
 	public boolean implementedBy(ProvidedInterface aDescriptor) {
 		Class[] provided = aDescriptor.getInterfaceTypes();
diff --git a/system/general/src/main/java/org/wamblee/system/core/RequiredInterface.java b/system/general/src/main/java/org/wamblee/system/core/RequiredInterface.java
index ce8d084c..bcd2635d 100644
--- a/system/general/src/main/java/org/wamblee/system/core/RequiredInterface.java
+++ b/system/general/src/main/java/org/wamblee/system/core/RequiredInterface.java
@@ -20,7 +20,13 @@ public interface RequiredInterface {
 	/**
 	 * Name for the interface. 
 	 */
-	public String getName(); 
+	String getName();
+	
+	/**
+	 * @return True iff the required interface is optional. 
+	 */
+	boolean isOptional(); 
+	
 	
 	/**
 	 * Checks if the service is provided by a given provided interface. 
diff --git a/system/general/src/test/java/org/wamblee/system/core/Application.java b/system/general/src/test/java/org/wamblee/system/core/Application.java
index 938f2bd1..16d1f36f 100644
--- a/system/general/src/test/java/org/wamblee/system/core/Application.java
+++ b/system/general/src/test/java/org/wamblee/system/core/Application.java
@@ -24,18 +24,22 @@ import org.wamblee.system.core.RequiredInterface;
 import org.wamblee.test.EventTracker;
 
 public class Application extends AbstractComponent {
-	private static RequiredInterface[] required() {
+	public static RequiredInterface[] required(boolean aOptional) {
 		return
 		new RequiredInterface[] { 
-    			new DefaultRequiredInterface("datasource", DataSource.class), 
-    			new DefaultRequiredInterface("integer", Integer.class)
+    			new DefaultRequiredInterface("datasource", DataSource.class, aOptional), 
+    			new DefaultRequiredInterface("integer", Integer.class, aOptional)
     	};
 	}
 
 	private EventTracker<String> _tracker;
 	
 	public Application() {
-		super("application", new ProvidedInterface[0], required()); 
+		super("application", new ProvidedInterface[0], required(false)); 
+	}
+	
+	public Application(boolean aIsOptinal) { 
+		super("application", new ProvidedInterface[0], required(true)); 
 	}
 	
 	public Application(EventTracker<String> aTracker) { 
diff --git a/system/general/src/test/java/org/wamblee/system/core/ContainerTest.java b/system/general/src/test/java/org/wamblee/system/core/ContainerTest.java
index 5a7fa90b..27a1937f 100644
--- a/system/general/src/test/java/org/wamblee/system/core/ContainerTest.java
+++ b/system/general/src/test/java/org/wamblee/system/core/ContainerTest.java
@@ -338,4 +338,50 @@ public class ContainerTest extends TestCase {
 		}
 		fail();
 	}
+	
+	public void testOptionalRequiredInterfaceProvidedOptionalInternal() {
+		Application application = new Application(true);
+		Container container = new Container("top", new Component[] { application }, 
+				new ProvidedInterface[0], Application.required(true));
+		Environment env = new Environment();
+		container.getRequiredInterfaces()[0].setProvider(
+				env.getProvidedInterfaces()[0]); 
+		container.getRequiredInterfaces()[1].setProvider(
+				env.getProvidedInterfaces()[1]);
+	    container.start();
+	    assertSame(env.getProvidedInterfaces()[0], container.getRequiredInterfaces()[0].getProvider());
+	    assertSame(env.getProvidedInterfaces()[1], container.getRequiredInterfaces()[1].getProvider());
+	    assertSame(env.getProvidedInterfaces()[0], application.getRequiredInterfaces()[0].getProvider());
+	    assertSame(env.getProvidedInterfaces()[1], application.getRequiredInterfaces()[1].getProvider());
+	}
+	
+	public void testOptionalRequiredInterfaceNotProvidedOptionalInternal() {
+		Application application = new Application(true);
+		Container container = new Container("top", new Component[] { application }, 
+				new ProvidedInterface[0], Application.required(true));
+		Environment env = new Environment();
+		container.getRequiredInterfaces()[0].setProvider(
+				env.getProvidedInterfaces()[0]); 
+	    container.start();
+	    assertSame(env.getProvidedInterfaces()[0], container.getRequiredInterfaces()[0].getProvider());
+	    assertNull(container.getRequiredInterfaces()[1].getProvider());
+	    assertSame(env.getProvidedInterfaces()[0], application.getRequiredInterfaces()[0].getProvider());
+	    assertNull(application.getRequiredInterfaces()[1].getProvider());
+	}
+	
+	public void testOptionalRequiredInterfaceProvidedMandatoryInternal() { 
+		Application application = new Application();
+		Container container = new Container("top", new Component[] { application }, 
+				new ProvidedInterface[0], Application.required(true));
+		Environment env = new Environment();
+		container.getRequiredInterfaces()[0].setProvider(
+				env.getProvidedInterfaces()[0]); 
+		container.getRequiredInterfaces()[1].setProvider(
+				env.getProvidedInterfaces()[1]);
+	    container.start();
+	    assertSame(env.getProvidedInterfaces()[0], container.getRequiredInterfaces()[0].getProvider());
+	    assertSame(env.getProvidedInterfaces()[1], container.getRequiredInterfaces()[1].getProvider());
+	    assertSame(env.getProvidedInterfaces()[0], application.getRequiredInterfaces()[0].getProvider());
+	    assertSame(env.getProvidedInterfaces()[1], application.getRequiredInterfaces()[1].getProvider());	
+	}
 }
-- 
2.31.1