Bug 1385 : Adding support for toString, equals and hashCode for the Typed Interfaces. 37/9237/2
authorMadhu Venugopal <mavenugo@gmail.com>
Tue, 22 Jul 2014 14:10:18 +0000 (07:10 -0700)
committerMadhu Venugopal <mavenugo@gmail.com>
Tue, 22 Jul 2014 20:42:15 +0000 (13:42 -0700)
Guava's AbstractInvocationHandler indeed handles these missing methods on top of InvocationHander (that we currently use).
But unfortunately, the equals() support in AbstractInvocationHandler is screwed up and doesnt fit our needs.
More to it the invoke() method in AbstractInvocationHandler is declared as final.

Hence have implemented a similar functionality on top of InvocationHandler.

In order to have a proper hashCode and equals for these Typed Interfaces, I had to add these methods to all the
Library schema unTyped classes such as Row, Column, BaseType, etc...

Also added IT to cover these methods.

Change-Id: Ia5c3081ae5ce20b9a8fd6ceaadbfd21145649078
Signed-off-by: Madhu Venugopal <mavenugo@gmail.com>
library/src/main/java/org/opendaylight/ovsdb/lib/notation/Column.java
library/src/main/java/org/opendaylight/ovsdb/lib/notation/Row.java
library/src/main/java/org/opendaylight/ovsdb/lib/schema/BaseType.java
library/src/main/java/org/opendaylight/ovsdb/lib/schema/ColumnSchema.java
library/src/main/java/org/opendaylight/ovsdb/lib/schema/ColumnType.java
library/src/main/java/org/opendaylight/ovsdb/lib/schema/typed/TyperUtils.java
schemas/openvswitch/src/test/java/org/opendaylight/ovsdb/schema/openvswitch/OpenVswitchSchemaSuiteIT.java
schemas/openvswitch/src/test/java/org/opendaylight/ovsdb/schema/openvswitch/TyperUtilsSpecialMethodsTestCases.java [new file with mode: 0644]

index 48ecc99514b9dbcf53c2cc5299c07bdf3e4e1825..ad0b2492a0696b3611827706d3a8483b775c10ac 100644 (file)
@@ -53,4 +53,35 @@ public class Column<E extends TableSchema<E>, D> {
     public String toString() {
         return "Column [schema=" + schema + ", data=" + data + "]";
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((data == null) ? 0 : data.hashCode());
+        result = prime * result + ((schema == null) ? 0 : schema.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        Column other = (Column) obj;
+        if (data == null) {
+            if (other.data != null)
+                return false;
+        } else if (!data.equals(other.data))
+            return false;
+        if (schema == null) {
+            if (other.schema != null)
+                return false;
+        } else if (!schema.equals(other.schema))
+            return false;
+        return true;
+    }
 }
index c1e1b16c3d56b42ed5721e9b575cc931d92ef725..1d813fd4040482f9c9918cac32d2f79ea5920234 100644 (file)
@@ -71,4 +71,41 @@ public class Row<E extends TableSchema<E>> {
     public String toString() {
         return "Row [columns=" + columns + "]";
     }
+
+    /**
+     * The hashCode method for Row object should be used with caution.
+     * This method will use all the columns in the row to calculate the hashKey.
+     * Hence using this method on a partial Row will return a different hashKey
+     * and will not work in most of the use-cases this method might be used.
+     */
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((columns == null) ? 0 : columns.hashCode());
+        return result;
+    }
+
+    /**
+     * The equals method for Row object should be used with caution.
+     * This method will compare all the columns in the row being compared.
+     * Hence using this method to compare a partial Row will return false
+     * and will not work in most of the use-cases this method might be used.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        Row other = (Row) obj;
+        if (columns == null) {
+            if (other.columns != null)
+                return false;
+        } else if (!columns.equals(other.columns))
+            return false;
+        return true;
+    }
 }
index 2addab460dc4c188e03f10ab35e815fd9c5edfad..be9149f3a2cdebca5882e8017045677dc5fe72ba 100644 (file)
@@ -11,11 +11,12 @@ package org.opendaylight.ovsdb.lib.schema;
 
 import java.util.Set;
 
-import com.fasterxml.jackson.databind.JsonNode;
-import com.google.common.collect.Sets;
 import org.opendaylight.ovsdb.lib.error.TyperException;
 import org.opendaylight.ovsdb.lib.notation.UUID;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.Sets;
+
 public abstract class BaseType<E extends BaseType<E>> {
 
     private static BaseType[] types = new BaseType[]{
@@ -157,6 +158,37 @@ public abstract class BaseType<E extends BaseType<E>> {
         public String toString() {
             return "IntegerBaseType";
         }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((enums == null) ? 0 : enums.hashCode());
+            result = prime * result + (int) (max ^ (max >>> 32));
+            result = prime * result + (int) (min ^ (min >>> 32));
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            IntegerBaseType other = (IntegerBaseType) obj;
+            if (enums == null) {
+                if (other.enums != null)
+                    return false;
+            } else if (!enums.equals(other.enums))
+                return false;
+            if (max != other.max)
+                return false;
+            if (min != other.min)
+                return false;
+            return true;
+        }
     }
 
     public static class RealBaseType extends BaseType<RealBaseType> {
@@ -233,6 +265,42 @@ public abstract class BaseType<E extends BaseType<E>> {
         public String toString() {
             return "RealBaseType";
         }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((enums == null) ? 0 : enums.hashCode());
+            long temp;
+            temp = Double.doubleToLongBits(max);
+            result = prime * result + (int) (temp ^ (temp >>> 32));
+            temp = Double.doubleToLongBits(min);
+            result = prime * result + (int) (temp ^ (temp >>> 32));
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            RealBaseType other = (RealBaseType) obj;
+            if (enums == null) {
+                if (other.enums != null)
+                    return false;
+            } else if (!enums.equals(other.enums))
+                return false;
+            if (Double.doubleToLongBits(max) != Double
+                    .doubleToLongBits(other.max))
+                return false;
+            if (Double.doubleToLongBits(min) != Double
+                    .doubleToLongBits(other.min))
+                return false;
+            return true;
+        }
     }
 
 
@@ -345,6 +413,37 @@ public abstract class BaseType<E extends BaseType<E>> {
             return "StringBaseType";
         }
 
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((enums == null) ? 0 : enums.hashCode());
+            result = prime * result + maxLength;
+            result = prime * result + minLength;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            StringBaseType other = (StringBaseType) obj;
+            if (enums == null) {
+                if (other.enums != null)
+                    return false;
+            } else if (!enums.equals(other.enums))
+                return false;
+            if (maxLength != other.maxLength)
+                return false;
+            if (minLength != other.minLength)
+                return false;
+            return true;
+        }
+
     }
 
 
@@ -408,5 +507,35 @@ public abstract class BaseType<E extends BaseType<E>> {
         public String toString() {
             return "UuidBaseType";
         }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result
+                    + ((refTable == null) ? 0 : refTable.hashCode());
+            result = prime * result
+                    + ((refType == null) ? 0 : refType.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            UuidBaseType other = (UuidBaseType) obj;
+            if (refTable == null) {
+                if (other.refTable != null)
+                    return false;
+            } else if (!refTable.equals(other.refTable))
+                return false;
+            if (refType != other.refType)
+                return false;
+            return true;
+        }
     }
 }
index 979f4fc3db8bc52bf2ff2536f4616f3bc9267a90..78fc50dc43213917134705b3d8ad418944443c66 100644 (file)
@@ -83,6 +83,37 @@ public class ColumnSchema<E extends TableSchema<E>, D> {
                 '}';
     }
 
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + ((type == null) ? 0 : type.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ColumnSchema other = (ColumnSchema) obj;
+        if (name == null) {
+            if (other.name != null)
+                return false;
+        } else if (!name.equals(other.name))
+            return false;
+        if (type == null) {
+            if (other.type != null)
+                return false;
+        } else if (!type.equals(other.type))
+            return false;
+        return true;
+    }
+
     /**
      * Validates the passed in value against the constraints set for this ColumnSchema
      * @param value
index a047cd6d194082ea245589c6ad1c726f298b97be..bb919a1cf16d327995489606adf56c0f79ab4a50 100644 (file)
@@ -122,6 +122,38 @@ public abstract class ColumnType {
                 '}';
     }
 
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result
+                + ((baseType == null) ? 0 : baseType.hashCode());
+        result = prime * result + (int) (max ^ (max >>> 32));
+        result = prime * result + (int) (min ^ (min >>> 32));
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        ColumnType other = (ColumnType) obj;
+        if (baseType == null) {
+            if (other.baseType != null)
+                return false;
+        } else if (!baseType.equals(other.baseType))
+            return false;
+        if (max != other.max)
+            return false;
+        if (min != other.min)
+            return false;
+        return true;
+    }
+
     public static class AtomicColumnType extends ColumnType {
         static final org.slf4j.Logger logger = LoggerFactory.getLogger(AtomicColumnType.class);
         public AtomicColumnType() {
@@ -256,5 +288,36 @@ public abstract class ColumnType {
         public void validate(Object value) {
             this.baseType.validate(value);
         }
+
+        @Override
+        public String toString() {
+            return "KeyValuedColumnType [keyType=" + keyType + " "+ super.toString() +"]";
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = super.hashCode();
+            result = prime * result
+                    + ((keyType == null) ? 0 : keyType.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!super.equals(obj))
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            KeyValuedColumnType other = (KeyValuedColumnType) obj;
+            if (keyType == null) {
+                if (other.keyType != null)
+                    return false;
+            } else if (!keyType.equals(other.keyType))
+                return false;
+            return true;
+        }
     }
 }
index 548d30e8bd2da35e88e97d390b74dfd3e0a0157c..aea5e97426c6069ec318c8efea073e7a424d97a7 100644 (file)
@@ -17,12 +17,14 @@ import org.opendaylight.ovsdb.lib.error.ColumnSchemaNotFoundException;
 import org.opendaylight.ovsdb.lib.error.SchemaVersionMismatchException;
 import org.opendaylight.ovsdb.lib.error.TableSchemaNotFoundException;
 import org.opendaylight.ovsdb.lib.error.TyperException;
+import org.opendaylight.ovsdb.lib.error.UnsupportedMethodException;
 import org.opendaylight.ovsdb.lib.notation.Column;
 import org.opendaylight.ovsdb.lib.notation.Row;
 import org.opendaylight.ovsdb.lib.notation.Version;
 import org.opendaylight.ovsdb.lib.schema.ColumnSchema;
 import org.opendaylight.ovsdb.lib.schema.DatabaseSchema;
 import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
+import org.opendaylight.ovsdb.lib.schema.TableSchema;
 
 import com.google.common.reflect.Reflection;
 
@@ -307,6 +309,18 @@ public class TyperUtils {
                 return getTableSchema(dbSchema, klazz);
             }
 
+            private Boolean isHashCodeMethod(Method method, Object[] args) {
+                return (args == null || args.length == 0) && method.getName().equals("hashCode");
+            }
+            private Boolean isEqualsMethod(Method method, Object[] args) {
+                return (args != null
+                        && args.length == 1
+                        && method.getName().equals("equals")
+                        && method.getParameterTypes()[0] == Object.class);
+            }
+            private Boolean isToStringMethod(Method method, Object[] args) {
+                return (args == null || args.length == 0) && method.getName().equals("toString");
+            }
             @Override
             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                 if (isGetTableSchema(method)) {
@@ -319,14 +333,40 @@ public class TyperUtils {
                     return processGetData(method);
                 } else if(isGetColumn(method)) {
                     return processGetColumn(method);
-                } else {
-                    /*
-                     *  TODO : Handle the methods provided by Object class (such as toString, hashCode, equals, etc.).
-                     *  Reintroduce throwing RuntimeException("Unsupported method : "+method.getName()); after these methods
-                     *  are handled
-                     */
-                    return null;
+                } else if (isHashCodeMethod(method, args)) {
+                    return hashCode();
+                } else if (isEqualsMethod(method, args)) {
+                    return proxy.getClass().isInstance(args[0]) && this.equals(args[0]);
+                } else if (isToStringMethod(method, args)) {
+                    return this.toString();
+                }
+                throw new UnsupportedMethodException("Method not supported "+method.toString());
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                if (obj == null) return false;
+                TypedBaseTable<?> typedRowObj = (TypedBaseTable<?>)obj;
+                if (row == null && typedRowObj.getRow() == null) return true;
+                if (row.equals(typedRowObj.getRow())) return true;
+                return false;
+            }
+
+            @Override public int hashCode() {
+                if (row == null) return 0;
+                return row.hashCode();
+            }
+
+            @Override public String toString() {
+                String tableName = null;
+                try {
+                    TableSchema<?> schema = (TableSchema<?>)processGetTableSchema();
+                    tableName = schema.getName();
+                } catch (Throwable e) {
+                    tableName = "";
                 }
+                if (row == null) return tableName;
+                return tableName+" : "+row.toString();
             }
         }
         );
index 150c3c39098a8537a02ea2f83e9dd5e574272355..1901ecee7efc58a9a9f18a460101664863a462c1 100644 (file)
@@ -35,6 +35,7 @@ import org.opendaylight.ovsdb.lib.schema.DatabaseSchema;
         QueueTestCases.class,
         ManagerTestCases.class,
         MirrorTestCases.class,
+        TyperUtilsSpecialMethodsTestCases.class,
         TearDown.class
 })
 public class OpenVswitchSchemaSuiteIT {
diff --git a/schemas/openvswitch/src/test/java/org/opendaylight/ovsdb/schema/openvswitch/TyperUtilsSpecialMethodsTestCases.java b/schemas/openvswitch/src/test/java/org/opendaylight/ovsdb/schema/openvswitch/TyperUtilsSpecialMethodsTestCases.java
new file mode 100644 (file)
index 0000000..cfbda88
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2014 Red Hat, Inc.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *
+ *  Authors : Madhu Venugopal
+ */
+
+package org.opendaylight.ovsdb.schema.openvswitch;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.ovsdb.lib.message.UpdateNotification;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+;
+
+public class TyperUtilsSpecialMethodsTestCases extends OpenVswitchSchemaTestBase {
+    Logger logger = LoggerFactory.getLogger(TyperUtilsSpecialMethodsTestCases.class);
+
+    @Override
+    @Before
+    public void setUp() throws ExecutionException, InterruptedException, TimeoutException, IOException {
+        super.setUp();
+     }
+
+    @Test
+    public void testToString() throws IOException, InterruptedException, ExecutionException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+        Bridge bridge = this.ovs.createTypedRowWrapper(Bridge.class);
+        assertNotNull(bridge);
+        bridge.setName(TEST_BRIDGE_NAME);
+        bridge.setStatus(ImmutableMap.of("key", "value"));
+        bridge.setFloodVlans(Sets.newHashSet(34L));
+        assertNotNull(bridge.toString());
+
+        Bridge nullRowBridge = this.ovs.getTypedRowWrapper(Bridge.class, null);
+        assertNotNull(nullRowBridge.toString());
+    }
+
+    @Test
+    public void testEquals() throws IOException, InterruptedException, ExecutionException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+        Bridge bridge = this.ovs.createTypedRowWrapper(Bridge.class);
+        assertNotNull(bridge);
+        bridge.setName(TEST_BRIDGE_NAME);
+        bridge.setStatus(ImmutableMap.of("key", "value"));
+        bridge.setFloodVlans(Sets.newHashSet(34L));
+
+        assertTrue("Equals check on same Bridge object", bridge.equals(bridge));
+
+        Bridge bridge2 = this.ovs.createTypedRowWrapper(Bridge.class);
+        assertNotNull(bridge2);
+        bridge2.setName(bridge.getName());
+        bridge2.setStatus(bridge.getStatusColumn().getData());
+        bridge2.setFloodVlans(bridge.getFloodVlansColumn().getData());
+
+        assertTrue("Equals check for different Bridge objects with same content", bridge.equals(bridge2));
+
+        bridge2.setStpEnable(true);
+        assertFalse("Equals check for different Bridge objects with different content", bridge.equals(bridge2));
+
+        Port port = this.ovs.createTypedRowWrapper(Port.class);
+        port.setName(bridge.getName());
+        assertFalse("Equals check for a Bridge object and Port Object", bridge.equals(port));
+        assertFalse("Equals check for a Typed Proxy object and non-proxy object", port.equals("String"));
+
+        Bridge nullRowBridge = this.ovs.getTypedRowWrapper(Bridge.class, null);
+        assertTrue("Equals check on Bridge object with null Row", nullRowBridge.equals(nullRowBridge));
+    }
+
+    @Test
+    public void testHashCode() throws IOException, InterruptedException, ExecutionException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+        Bridge bridge = this.ovs.createTypedRowWrapper(Bridge.class);
+
+        assertNotNull(bridge);
+        bridge.setName(TEST_BRIDGE_NAME);
+        bridge.setStatus(ImmutableMap.of("key", "value"));
+        bridge.setFloodVlans(Sets.newHashSet(34L));
+
+        assertNotSame(bridge.hashCode(), 0);
+        Bridge nullRowBridge = this.ovs.getTypedRowWrapper(Bridge.class, null);
+        assertSame(nullRowBridge.hashCode(), 0);
+    }
+
+    @Override
+    public void update(Object context, UpdateNotification upadateNotification) {
+
+    }
+
+    @Override
+    public void locked(Object context, List<String> ids) {
+
+    }
+
+    @Override
+    public void stolen(Object context, List<String> ids) {
+
+    }
+}