Introduce ResourcePath.Key

Change-Id: I4efd5c13a12f2bad5482f5b432e2f1ef2c337805
diff --git a/core/api/src/main/java/org/onosproject/net/newresource/ResourcePath.java b/core/api/src/main/java/org/onosproject/net/newresource/ResourcePath.java
index 72f8ac0..d07e516 100644
--- a/core/api/src/main/java/org/onosproject/net/newresource/ResourcePath.java
+++ b/core/api/src/main/java/org/onosproject/net/newresource/ResourcePath.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 Open Networking Laboratory
+ * Copyright 2015-2016 Open Networking Laboratory
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,7 +21,6 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.PortNumber;
 
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
@@ -50,12 +49,12 @@
 public abstract class ResourcePath {
 
     private final Discrete parent;
-    private final Object last;
+    private final Key key;
 
     public static final Discrete ROOT = new Discrete();
 
     public static ResourcePath discrete(DeviceId device) {
-        return new Discrete(ImmutableList.of(device));
+        return new Discrete(Key.of(device));
     }
 
     /**
@@ -66,10 +65,7 @@
      * @return resource path instance
      */
     public static ResourcePath discrete(DeviceId device, Object... components) {
-        return new Discrete(ImmutableList.builder()
-                .add(device)
-                .add(components)
-                .build());
+        return new Discrete(Key.of(device, components));
     }
 
     /**
@@ -81,11 +77,7 @@
      * @return resource path instance
      */
     public static ResourcePath discrete(DeviceId device, PortNumber port, Object... components) {
-        return new Discrete(ImmutableList.builder()
-                .add(device)
-                .add(port)
-                .add(components)
-                .build());
+        return new Discrete(Key.of(device, port, components));
     }
 
     /**
@@ -100,10 +92,7 @@
         checkArgument(components.length > 0,
                 "Length of components must be greater thant 0, but " + components.length);
 
-        return new Continuous(ImmutableList.builder()
-                .add(device)
-                .add(components)
-                .build(), value);
+        return new Continuous(Key.of(device, components), value);
     }
 
     /**
@@ -116,49 +105,29 @@
      * @return resource path instance
      */
     public static ResourcePath continuous(double value, DeviceId device, PortNumber port, Object... components) {
-        return new Continuous(ImmutableList.builder()
-                .add(device)
-                .add(port)
-                .add(components)
-                .build(), value);
+        return new Continuous(Key.of(device, port, components), value);
     }
 
     /**
-     * Creates an resource path from the specified components.
+     * Creates an resource path from the specified key.
      *
-     * @param components components of the path. The order represents hierarchical structure of the resource.
+     * @param key key of the path
      */
-    protected ResourcePath(List<Object> components) {
-        checkNotNull(components);
-        checkArgument(!components.isEmpty());
+    protected ResourcePath(Key key) {
+        checkNotNull(key);
 
-        LinkedList<Object> children = new LinkedList<>(components);
-        this.last = children.pollLast();
-        if (children.isEmpty()) {
+        this.key = key;
+        if (key.components.size() == 1) {
             this.parent = ROOT;
         } else {
-            this.parent = new Discrete(children);
+            this.parent = new Discrete(key.parent());
         }
     }
 
-    /**
-     * Creates an resource path from the specified parent and child.
-     *
-     * @param parent the parent of this resource
-     * @param last a child of the parent
-     */
-    protected ResourcePath(Discrete parent, Object last) {
-        checkNotNull(parent);
-        checkNotNull(last);
-
-        this.parent = parent;
-        this.last = last;
-    }
-
     // for serialization
     private ResourcePath() {
         this.parent = null;
-        this.last = null;
+        this.key = Key.ROOT;
     }
 
     /**
@@ -167,15 +136,7 @@
      * @return the components of this resource path
      */
     public List<Object> components() {
-        LinkedList<Object> components = new LinkedList<>();
-
-        ResourcePath current = this;
-        while (current.parent().isPresent()) {
-            components.addFirst(current.last);
-            current = current.parent;
-        }
-
-        return components;
+        return key.components;
     }
 
     /**
@@ -199,7 +160,7 @@
     public ResourcePath child(Object child) {
         checkState(this instanceof Discrete);
 
-        return new Discrete((Discrete) this, child);
+        return new Discrete(key().child(child));
     }
 
     /**
@@ -213,7 +174,7 @@
     public ResourcePath child(Object child, double value) {
         checkState(this instanceof Discrete);
 
-        return new Continuous((Discrete) this, child, value);
+        return new Continuous(key.child(child), value);
     }
 
     /**
@@ -223,12 +184,24 @@
      * The return value is equal to the last object of {@code components()}.
      */
     public Object last() {
-        return last;
+        if (key.components.isEmpty()) {
+            return null;
+        }
+        return key.components.get(key.components.size() - 1);
+    }
+
+    /**
+     * Returns the key of this resource path.
+     *
+     * @return the key of this resource path
+     */
+    public Key key() {
+        return key;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(this.parent, this.last);
+        return key.hashCode();
     }
 
     @Override
@@ -240,15 +213,13 @@
             return false;
         }
         final ResourcePath that = (ResourcePath) obj;
-        return Objects.equals(this.parent, that.parent)
-                && Objects.equals(this.last, that.last);
+        return Objects.equals(this.key, that.key);
     }
 
     @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
-                .add("parent", parent)
-                .add("last", last)
+                .add("key", key)
                 .toString();
     }
 
@@ -266,12 +237,8 @@
             super();
         }
 
-        private Discrete(List<Object> components) {
-            super(components);
-        }
-
-        private Discrete(Discrete parent, Object last) {
-            super(parent, last);
+        private Discrete(Key key) {
+            super(key);
         }
     }
 
@@ -284,17 +251,34 @@
      */
     @Beta
     public static final class Continuous extends ResourcePath {
-        // Note: value is not taken into account for equality
         private final double value;
 
-        private Continuous(List<Object> components, double value) {
-            super(components);
+        private Continuous(Key key, double value) {
+            super(key);
             this.value = value;
         }
 
-        public Continuous(Discrete parent, Object last, double value) {
-            super(parent, last);
-            this.value = value;
+        @Override
+        public int hashCode() {
+            return Objects.hash(this.key(), this.value);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+
+            if (!(obj instanceof Continuous)) {
+                return false;
+            }
+
+            if (!super.equals(obj)) {
+                return false;
+            }
+
+            final Continuous other = (Continuous) obj;
+            return Objects.equals(this.key(), other.key());
         }
 
         /**
@@ -306,4 +290,80 @@
             return value;
         }
     }
+
+    /**
+     * Represents key of resource path used as a key in ResourceStore.
+     * This class is exposed to public, but intended to use only in ResourceStore implementations.
+     */
+    @Beta
+    public static final class Key {
+        private static final Key ROOT = new Key();
+
+        private final ImmutableList<Object> components;
+
+        private static Key of(DeviceId device, Object... components) {
+            return new Key(ImmutableList.builder()
+                    .add(device)
+                    .add(components)
+                    .build());
+        }
+
+        private static Key of(DeviceId device, PortNumber port, Object... components) {
+            return new Key(ImmutableList.builder()
+                    .add(device)
+                    .add(port)
+                    .add(components)
+                    .build());
+        }
+
+        private Key(ImmutableList<Object> components) {
+            this.components = checkNotNull(components);
+        }
+
+        // for serializer
+        private Key() {
+            this.components = ImmutableList.of();
+        }
+
+        // IndexOutOfBoundsException is raised when the instance is equal to ROOT
+        private Key parent() {
+            if (components.size() == 1) {
+                return ROOT;
+            } else {
+                return new Key(components.subList(0, components.size() - 1));
+            }
+        }
+
+        private Key child(Object child) {
+            return new Key(ImmutableList.builder()
+                    .add(components)
+                    .add(child)
+                    .build());
+        }
+
+        @Override
+        public int hashCode() {
+            return components.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof Key)) {
+                return false;
+            }
+
+            Key other = (Key) obj;
+            return Objects.equals(this.components, other.components);
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(this)
+                    .add("components", components)
+                    .toString();
+        }
+    }
 }
diff --git a/core/api/src/test/java/org/onosproject/net/newresource/ResourcePathTest.java b/core/api/src/test/java/org/onosproject/net/newresource/ResourcePathTest.java
index 4bbb458..23bbb27 100644
--- a/core/api/src/test/java/org/onosproject/net/newresource/ResourcePathTest.java
+++ b/core/api/src/test/java/org/onosproject/net/newresource/ResourcePathTest.java
@@ -43,12 +43,12 @@
         ResourcePath sameAsResource1 = ResourcePath.discrete(D1, P1, VLAN1);
         ResourcePath resource2 = ResourcePath.discrete(D2, P1, VLAN1);
         ResourcePath resource3 = ResourcePath.continuous(BW1.bps(), D1, P1, BW1);
-        ResourcePath sameAsResource3 = ResourcePath.continuous(BW2.bps(), D1, P1, BW1);
+        ResourcePath sameAsResource3 = ResourcePath.continuous(BW1.bps(), D1, P1, BW1);
 
         new EqualsTester()
                 .addEqualityGroup(resource1, sameAsResource1)
                 .addEqualityGroup(resource2)
-                .addEqualityGroup(resource3, sameAsResource3)   // this is intentional
+                .addEqualityGroup(resource3, sameAsResource3)
                 .testEquals();
     }
 
@@ -60,6 +60,21 @@
     }
 
     @Test
+    public void testKeyEquality() {
+        ResourcePath.Key key1 = ResourcePath.discrete(D1, P1, VLAN1).key();
+        ResourcePath.Key sameAsKey1 = ResourcePath.discrete(D1, P1, VLAN1).key();
+        ResourcePath.Key key2 = ResourcePath.discrete(D2, P1, VLAN1).key();
+        ResourcePath.Key key3 = ResourcePath.continuous(BW1.bps(), D1, P1, BW1).key();
+        // intentionally set a different value
+        ResourcePath.Key sameAsKey3 = ResourcePath.continuous(BW2.bps(), D1, P1, BW1).key();
+
+        new EqualsTester()
+                .addEqualityGroup(key1, sameAsKey1)
+                .addEqualityGroup(key2)
+                .addEqualityGroup(key3, sameAsKey3);
+    }
+
+    @Test
     public void testThereIsParent() {
         ResourcePath path = ResourcePath.discrete(D1, P1, VLAN1);
         ResourcePath parent = ResourcePath.discrete(D1, P1);
diff --git a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
index 657ad1e..f20a1e5 100644
--- a/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
+++ b/core/store/serializers/src/main/java/org/onosproject/store/serializers/KryoNamespaces.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2015 Open Networking Laboratory
+ * Copyright 2014-2016 Open Networking Laboratory
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -430,6 +430,7 @@
                     ResourcePath.class,
                     ResourcePath.Discrete.class,
                     ResourcePath.Continuous.class,
+                    ResourcePath.Key.class,
                     ResourceAllocation.class,
                     // Constraints
                     LambdaConstraint.class,
diff --git a/core/store/serializers/src/test/java/org/onosproject/store/serializers/KryoSerializerTest.java b/core/store/serializers/src/test/java/org/onosproject/store/serializers/KryoSerializerTest.java
index 7df518e..0d88e9f 100644
--- a/core/store/serializers/src/test/java/org/onosproject/store/serializers/KryoSerializerTest.java
+++ b/core/store/serializers/src/test/java/org/onosproject/store/serializers/KryoSerializerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014-2015 Open Networking Laboratory
+ * Copyright 2014-2016 Open Networking Laboratory
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -378,6 +378,11 @@
     }
 
     @Test
+    public void testResourceKey() {
+        testSerializedEquals(ResourcePath.discrete(DID1, P1).key());
+    }
+
+    @Test
     public void testResourceAllocation() {
         testSerializedEquals(new org.onosproject.net.newresource.ResourceAllocation(
                 ResourcePath.discrete(DID1, P1, VLAN1),