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>
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;
+ }
}
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;
+ }
}
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[]{
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> {
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;
+ }
}
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;
+ }
+
}
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;
+ }
}
}
'}';
}
+ @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
'}';
}
+ @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() {
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;
+ }
}
}
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;
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)) {
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();
}
}
);
QueueTestCases.class,
ManagerTestCases.class,
MirrorTestCases.class,
+ TyperUtilsSpecialMethodsTestCases.class,
TearDown.class
})
public class OpenVswitchSchemaSuiteIT {
--- /dev/null
+/*
+ * 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) {
+
+ }
+}