Add intent error handling features.

- added ErrorIntent class to express intent processing errors
- added ERROR to IntentOperation.Operator enum
- made IntentMap to change intents' state automatically when it processes ErrorIntent instances
- made PathCalcRuntime to create ErrorIntent instance for failed intents
- made PathCalcRuntimeModule to process ErrorIntent and reflect them to high-level intents' states

Change-Id: Ib0cf9c284a37812695864d75d3cde2499a76e2a7
diff --git a/src/main/java/net/onrc/onos/intent/IntentMap.java b/src/main/java/net/onrc/onos/intent/IntentMap.java
index 4657706..53667ad 100644
--- a/src/main/java/net/onrc/onos/intent/IntentMap.java
+++ b/src/main/java/net/onrc/onos/intent/IntentMap.java
@@ -5,6 +5,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
+import java.util.Map;
 import java.util.Map.Entry;
 
 import net.onrc.onos.intent.Intent.IntentState;
@@ -13,6 +14,10 @@
  * @author Toshio Koide (t-koide@onlab.us)
  */
 public class IntentMap {
+	private HashSet<ChangedListener> listeners = new HashSet<>();
+	private HashMap<String, Intent> intents = new HashMap<>();
+	private	LinkedList<ChangedEvent> events = new LinkedList<>();
+
 	public enum ChangedEventType {
 		/**
 		 * Added new intent.
@@ -47,47 +52,25 @@
 		void intentsChange(LinkedList<ChangedEvent> events);
 	}
 
-	private HashSet<ChangedListener> listeners = new HashSet<>();
-	private HashMap<String, Intent> intents = new HashMap<>();
-
-	protected void putIntent(Intent intent) {
-		if (intents.containsKey(intent.getId()))
-			removeIntent(intent.getId());
-		intents.put(intent.getId(), intent);
-	}
-
-	protected void removeIntent(String intentId) {
-		intents.remove(intentId);		
-	}
-
-	public Intent getIntent(String intentId) {
-		return intents.get(intentId);
-	}
+	//================================================================================
+	// public methods
+	//================================================================================
 
 	public void executeOperations(IntentOperationList operations) {
-		LinkedList<ChangedEvent> events = new LinkedList<>();
 		for (IntentOperation operation: operations) {
 			switch (operation.operator) {
 			case ADD:
-				putIntent(operation.intent);
-				events.add(new ChangedEvent(ChangedEventType.ADDED, operation.intent));
+				handleAddOperation(operation);
 				break;
 			case REMOVE:
-				Intent intent = getIntent(operation.intent.getId());
-				if (intent == null) {
-					// TODO throw exception
-				}
-				else {
-					intent.setState(Intent.IntentState.DEL_REQ);
-					events.add(new ChangedEvent(ChangedEventType.STATE_CHANGED,
-							new Intent(intent.getId(), Intent.IntentState.DEL_REQ)));
-				}
+				handleRemoveOperation(operation);
+				break;
+			case ERROR:
+				handleErrorOperation(operation);
 				break;
 			}
 		}
-		for (ChangedListener listener: listeners) {
-			listener.intentsChange(events);
-		}
+		notifyEvents();
 	}
 
 	public void purge() {
@@ -97,11 +80,23 @@
 			if (intent.getState() == IntentState.DEL_ACK
 					|| intent.getState() == IntentState.INST_NACK) {
 				removeIds.add(intent.getId());
-			}			
+			}
 		}
 		for (String intentId: removeIds) {
 			removeIntent(intentId);
 		}
+		notifyEvents();
+	}
+
+	public void changeStates(Map<String, IntentState> states) {
+		for (Entry<String, IntentState> state: states.entrySet()) {
+			setState(state.getKey(), state.getValue());
+		}
+		notifyEvents();
+	}
+
+	public Intent getIntent(String intentId) {
+		return intents.get(intentId);
 	}
 
 	public Collection<Intent> getAllIntents() {
@@ -115,4 +110,80 @@
 	public void removeChangedListener(ChangedListener listener) {
 		listeners.remove(listener);
 	}
+
+	//================================================================================
+	// methods that affect intents map (protected)
+	//================================================================================
+
+	protected void putIntent(Intent intent) {
+		if (intents.containsKey(intent.getId()))
+			removeIntent(intent.getId());
+		intents.put(intent.getId(), intent);
+		events.add(new ChangedEvent(ChangedEventType.ADDED, intent));
+	}
+
+	protected void removeIntent(String intentId) {
+		Intent intent = intents.remove(intentId);
+		if (intent == null) return;
+		events.add(new ChangedEvent(ChangedEventType.REMOVED, intent));
+	}
+
+	protected void setState(String intentId, IntentState state) {
+		Intent intent = intents.get(intentId);
+		if (intent == null) return;
+		intent.setState(state);
+		events.add(new ChangedEvent(ChangedEventType.STATE_CHANGED, intent));
+	}
+
+	//================================================================================
+	// helper methods (protected)
+	//================================================================================
+
+	protected void handleAddOperation(IntentOperation operation) {
+		putIntent(operation.intent);
+	}
+
+	protected void handleRemoveOperation(IntentOperation operation) {
+		Intent intent = getIntent(operation.intent.getId());
+		if (intent == null) {
+			// TODO error handling
+		}
+		else {
+			setState(intent.getId(), IntentState.DEL_REQ);
+		}
+	}
+
+	protected void handleErrorOperation(IntentOperation operation) {
+		//TODO put error message into the intent
+
+		ErrorIntent errorIntent = (ErrorIntent) operation.intent;
+		Intent targetIntent = intents.get(errorIntent.getId());
+		if (targetIntent == null) {
+			// TODO error handling
+			return;
+		}
+
+		switch (targetIntent.getState()) {
+		case CREATED:
+		case INST_REQ:
+		case INST_ACK:
+			setState(targetIntent.getId(), IntentState.INST_NACK);
+			break;
+		case DEL_REQ:
+			setState(targetIntent.getId(), IntentState.DEL_PENDING);
+			break;
+		case INST_NACK:
+		case DEL_PENDING:
+		case DEL_ACK:
+			// do nothing
+			break;
+		}
+	}
+
+	protected void notifyEvents() {
+		for (ChangedListener listener: listeners) {
+			listener.intentsChange(events);
+		}
+		events.clear();
+	}
 }