<name>ODL :: ovsdb :: ${project.artifactId}</name>
<properties>
+ <!-- Fails build for copy/pasted code -->
+ <pmd.cpd.fail>true</pmd.cpd.fail>
+
<sonar.jacoco.itReportPath>../it/target/jacoco-it.exec</sonar.jacoco.itReportPath>
</properties>
package org.opendaylight.ovsdb.lib.error;
+import com.google.common.collect.Range;
import org.opendaylight.ovsdb.lib.notation.Version;
/**
public class SchemaVersionMismatchException extends RuntimeException {
private static final long serialVersionUID = -5194270510726950745L;
- public SchemaVersionMismatchException(final Version schemaVersion, final Version fromVersion,
- final Version untilVersion) {
+ public SchemaVersionMismatchException(final Version schemaVersion, final Range<Version> range) {
super("The schema version used to access the table/column (" + schemaVersion + ") does not match the required"
- + " version (from " + fromVersion + " to " + untilVersion + ")");
+ + " version range " + range);
}
}
--- /dev/null
+/*
+ * Copyright © 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * 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
+ */
+package org.opendaylight.ovsdb.lib.schema.typed;
+
+import java.lang.reflect.Method;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.ovsdb.lib.notation.Column;
+import org.opendaylight.ovsdb.lib.notation.Row;
+import org.opendaylight.ovsdb.lib.schema.ColumnSchema;
+import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
+
+final class GetColumn<T> extends MethodDispatch.StrictColumnPrototype<T> {
+ private static final class Invoker<T> extends MethodDispatch.ColumnInvoker<T> {
+ Invoker(final @NonNull GenericTableSchema tableSchema,
+ final @NonNull ColumnSchema<GenericTableSchema, T> columnSchema) {
+ super(tableSchema, columnSchema);
+ }
+
+ @Override
+ Object invokeMethod(final Object proxy, final Object[] args) {
+ return new Column<>(columnSchema(), null);
+ }
+
+ @Override
+ Object invokeRowMethod(final Row<GenericTableSchema> row, final Object proxy, final Object[] args) {
+ return row.getColumn(columnSchema());
+ }
+ }
+
+ GetColumn(final Method method, final String tableName, final String columnName) {
+ super(method, tableName, columnName);
+ }
+
+ @Override
+ Invoker<T> bindToImpl(final GenericTableSchema tableSchema,
+ final ColumnSchema<GenericTableSchema, T> columnSchema) {
+ return new Invoker<>(tableSchema, columnSchema);
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * 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
+ */
+/*
+ * Copyright © 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * 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
+ */
+package org.opendaylight.ovsdb.lib.schema.typed;
+
+import java.lang.reflect.Method;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.ovsdb.lib.notation.Column;
+import org.opendaylight.ovsdb.lib.notation.Row;
+import org.opendaylight.ovsdb.lib.schema.ColumnSchema;
+import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
+
+final class GetData<T> extends MethodDispatch.StrictColumnPrototype<T> {
+ private static final class Invoker<T> extends MethodDispatch.ColumnInvoker<T> {
+ Invoker(final @NonNull GenericTableSchema tableSchema,
+ final @NonNull ColumnSchema<GenericTableSchema, T> columnSchema) {
+ super(tableSchema, columnSchema);
+ }
+
+ @Override
+ Object invokeMethod(final Object proxy, final Object[] args) {
+ return null;
+ }
+
+ @Override
+ Object invokeRowMethod(final Row<GenericTableSchema> row, final Object proxy, final Object[] args) {
+ final Column<GenericTableSchema, T> column = row.getColumn(columnSchema());
+ return column == null ? null : column.getData();
+ }
+ }
+
+ GetData(final Method method, final String tableName, final String columnName) {
+ super(method, tableName, columnName);
+ }
+
+ @Override
+ Invoker<T> bindToImpl(final GenericTableSchema tableSchema,
+ final ColumnSchema<GenericTableSchema, T> columnSchema) {
+ return new Invoker<>(tableSchema, columnSchema);
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * 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
+ */
+package org.opendaylight.ovsdb.lib.schema.typed;
+
+import org.opendaylight.ovsdb.lib.notation.Row;
+import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
+import org.opendaylight.ovsdb.lib.schema.typed.MethodDispatch.Prototype;
+
+final class GetRow extends Prototype {
+ private static final MethodDispatch.Invoker INVOKER = new MethodDispatch.Invoker() {
+ @Override
+ Object invokeMethod(final Row<GenericTableSchema> row, final Object proxy, final Object[] args) {
+ return row;
+ }
+ };
+
+ static final GetRow INSTANCE = new GetRow();
+
+ private GetRow() {
+
+ }
+
+ @Override
+ MethodDispatch.Invoker bindTo(final TypedDatabaseSchema dbSchema) {
+ return INVOKER;
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * 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
+ */
+package org.opendaylight.ovsdb.lib.schema.typed;
+
+import com.google.common.collect.Range;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.ovsdb.lib.notation.Row;
+import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
+
+public final class GetTable extends MethodDispatch.TablePrototype {
+ private static final class Invoker extends MethodDispatch.TableInvoker {
+ Invoker(final GenericTableSchema tableSchema) {
+ super(tableSchema);
+ }
+
+ @Override
+ @Nullable
+ Object invokeMethod(final Row<GenericTableSchema> row, final Object proxy, final Object[] args) {
+ return tableSchema();
+ }
+ }
+
+ GetTable(final String tableName) {
+ super(Range.all(), tableName);
+ }
+
+ @Override
+ Invoker bindToImpl(final TypedDatabaseSchema dbSchema) {
+ return new Invoker(findTableSchema(dbSchema));
+ }
+}
+
--- /dev/null
+/*
+ * Copyright © 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * 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
+ */
+package org.opendaylight.ovsdb.lib.schema.typed;
+
+import static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Range;
+import java.lang.reflect.Method;
+import java.util.Locale;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+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.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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Table to Method runtime-constant support. The binding of Class methods to corresponding data operations is defined
+ * by annotations, which means that such mapping is Class-invariant. This invariance is captured in this class.
+ *
+ * <p>
+ * Data operations are always invoked in the context of a runtime {@link DatabaseSchema}, i.e. for a particular device
+ * or a device function. This class exposes {@link #bindToSchema(TypedDatabaseSchema)}, which will construct an
+ * immutable mapping between a Method and its invocation handler.
+ */
+final class MethodDispatch {
+ abstract static class Invoker {
+
+ abstract Object invokeMethod(Row<GenericTableSchema> row, Object proxy, Object[] args);
+ }
+
+ abstract static class Prototype {
+
+ abstract Invoker bindTo(TypedDatabaseSchema dbSchema);
+ }
+
+ abstract static class FailedInvoker extends Invoker {
+ @Override
+ final Object invokeMethod(final Row<GenericTableSchema> row, final Object proxy, final Object[] args) {
+ throw newException();
+ }
+
+ abstract @NonNull RuntimeException newException();
+ }
+
+ abstract static class TableInvoker extends Invoker {
+ private final GenericTableSchema tableSchema;
+
+ TableInvoker(final GenericTableSchema tableSchema) {
+ this.tableSchema = tableSchema;
+ }
+
+ @Nullable GenericTableSchema tableSchema() {
+ return tableSchema;
+ }
+ }
+
+ abstract static class ColumnInvoker<T> extends TableInvoker {
+ private final ColumnSchema<GenericTableSchema, T> columnSchema;
+
+ ColumnInvoker(final GenericTableSchema tableSchema, final ColumnSchema<GenericTableSchema, T> columnSchema) {
+ super(requireNonNull(tableSchema));
+ this.columnSchema = columnSchema;
+ }
+
+ @Override
+ final @NonNull GenericTableSchema tableSchema() {
+ return verifyNotNull(super.tableSchema());
+ }
+
+ @Nullable ColumnSchema<GenericTableSchema, T> columnSchema() {
+ return columnSchema;
+ }
+
+ @Override
+ Object invokeMethod(final Row<GenericTableSchema> row, final Object proxy, final Object[] args) {
+ // When the row is null, that might indicate that the user maybe interested
+ // only in the ColumnSchema and not on the Data.
+ return row == null ? invokeMethod(proxy, args) : invokeRowMethod(row, proxy, args);
+ }
+
+ abstract Object invokeMethod(Object proxy, Object[] args);
+
+ abstract Object invokeRowMethod(@NonNull Row<GenericTableSchema> row, Object proxy, Object[] args);
+ }
+
+ // As the mode of invocation for a particular method is invariant, we keep the set of dynamically-supported method
+ // in a per-Class cache, thus skipping reflective operations at invocation time.
+ private static final LoadingCache<Class<?>, MethodDispatch> CACHE = CacheBuilder.newBuilder()
+ .weakKeys().weakValues().build(new CacheLoader<Class<?>, MethodDispatch>() {
+ @Override
+ public MethodDispatch load(final Class<?> key) {
+ return new MethodDispatch(key);
+ }
+ });
+
+ private abstract static class VersionedPrototype extends Prototype {
+ private static final Logger LOG = LoggerFactory.getLogger(VersionedPrototype.class);
+
+ private final Range<Version> supportedVersions;
+
+ VersionedPrototype(final Range<Version> supportedVersions) {
+ this.supportedVersions = requireNonNull(supportedVersions);
+ }
+
+ @Override
+ final Invoker bindTo(final TypedDatabaseSchema dbSchema) {
+ final Version version = dbSchema.getVersion();
+ if (supportedVersions.contains(version)) {
+ return bindToImpl(dbSchema);
+ }
+
+ LOG.debug("Version {} does not match required range {}, deferring failure to invocation time", version,
+ supportedVersions);
+ return new FailedInvoker() {
+ @Override
+ RuntimeException newException() {
+ return new SchemaVersionMismatchException(version, supportedVersions);
+ }
+ };
+ }
+
+ abstract Invoker bindToImpl(TypedDatabaseSchema dbSchema);
+ }
+
+ abstract static class TablePrototype extends VersionedPrototype {
+ private static final Logger LOG = LoggerFactory.getLogger(TablePrototype.class);
+
+ private final String tableName;
+
+ TablePrototype(final Range<Version> supportedVersions, final String tableName) {
+ super(supportedVersions);
+ this.tableName = requireNonNull(tableName);
+ }
+
+ final @Nullable GenericTableSchema findTableSchema(final DatabaseSchema dbSchema) {
+ return dbSchema.table(tableName, GenericTableSchema.class);
+ }
+
+ final @NonNull FailedInvoker tableSchemaNotFound(final DatabaseSchema dbSchema) {
+ final String dbName = dbSchema.getName();
+ LOG.debug("Failed to find schema for table {} in {}, deferring failure to invocation time", tableName,
+ dbName);
+ return new FailedInvoker() {
+ @Override
+ RuntimeException newException() {
+ return new TableSchemaNotFoundException(tableName, dbName);
+ }
+ };
+ }
+ }
+
+ abstract static class ColumnPrototype<T> extends TablePrototype {
+ private static final Logger LOG = LoggerFactory.getLogger(ColumnPrototype.class);
+
+ private final @NonNull Class<T> columnType;
+ private final @NonNull String columnName;
+
+ ColumnPrototype(final Method method, final Class<?> columnType, final String tableName,
+ final String columnName) {
+ super(TypedReflections.getColumnVersionRange(method), tableName);
+ this.columnName = requireNonNull(columnName);
+ this.columnType = requireNonNull((Class<T>) columnType);
+ }
+
+ final @NonNull String columnName() {
+ return columnName;
+ }
+
+ final @Nullable ColumnSchema<GenericTableSchema, T> findColumnSchema(
+ final @NonNull GenericTableSchema tableSchema) {
+ return tableSchema.column(columnName, columnType);
+ }
+
+ final @NonNull FailedInvoker columnSchemaNotFound(final @NonNull GenericTableSchema tableSchema) {
+ final String tableName = tableSchema.getName();
+ LOG.debug("Failed to find schema for column {} in {}, deferring failure to invocation time", columnName,
+ tableName);
+ return new FailedInvoker() {
+ @Override
+ RuntimeException newException() {
+ return new ColumnSchemaNotFoundException(columnName, tableName);
+ }
+ };
+ }
+
+ @Override
+ final Invoker bindToImpl(final TypedDatabaseSchema dbSchema) {
+ final GenericTableSchema tableSchema = findTableSchema(dbSchema);
+ return tableSchema != null ? bindToImpl(tableSchema) : tableSchemaNotFound(dbSchema);
+ }
+
+ abstract Invoker bindToImpl(@NonNull GenericTableSchema tableSchema);
+ }
+
+ abstract static class StrictColumnPrototype<T> extends ColumnPrototype<T> {
+
+ StrictColumnPrototype(final Method method, final String tableName, final String columnName) {
+ super(method, method.getReturnType(), tableName, columnName);
+ }
+
+ @Override
+ final Invoker bindToImpl(@NonNull final GenericTableSchema tableSchema) {
+ final ColumnSchema<GenericTableSchema, T> columnSchema = findColumnSchema(tableSchema);
+ return columnSchema != null ? bindToImpl(tableSchema, columnSchema) : columnSchemaNotFound(tableSchema);
+
+ }
+
+ abstract Invoker bindToImpl(@NonNull GenericTableSchema tableSchema,
+ @NonNull ColumnSchema<GenericTableSchema, T> columnSchema);
+ }
+
+ private final @NonNull ImmutableMap<Method, Prototype> prototypes;
+ private final @NonNull String tableName;
+
+ private MethodDispatch(final Class<?> key) {
+ tableName = TypedReflections.getTableName(key);
+
+ final ImmutableMap.Builder<Method, Prototype> builder = ImmutableMap.builder();
+ for (Method method : key.getMethods()) {
+ final Prototype prototype = MethodDispatch.prototypeFor(tableName, method);
+ if (prototype != null) {
+ builder.put(method, prototype);
+ }
+ }
+ this.prototypes = builder.build();
+ }
+
+ static MethodDispatch forTarget(final Class<?> target) {
+ return CACHE.getUnchecked(target);
+ }
+
+ @NonNull TypedRowInvocationHandler bindToSchema(final TypedDatabaseSchema dbSchema) {
+ return new TypedRowInvocationHandler(tableName,
+ ImmutableMap.copyOf(Maps.transformValues(prototypes, prototype -> prototype.bindTo(dbSchema))));
+ }
+
+ private static @Nullable Prototype prototypeFor(final String tableName, final Method method) {
+ final TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
+ if (typedColumn != null) {
+ final MethodType methodType = typedColumn.method();
+ switch (methodType) {
+ case GETCOLUMN:
+ return new GetColumn<>(method, tableName, typedColumn.name());
+ case GETDATA:
+ return new GetData<>(method, tableName, typedColumn.name());
+ case GETROW:
+ return GetRow.INSTANCE;
+ case GETTABLESCHEMA:
+ return new GetTable(tableName);
+ case SETDATA:
+ return new SetData<>(method, tableName, typedColumn.name());
+ default:
+ throw new TyperException("Unhandled method type " + methodType);
+ }
+ }
+
+ /*
+ * Attempting to get the column name by parsing the method name with a following convention :
+ * 1. GETDATA : get<ColumnName>
+ * 2. SETDATA : set<ColumnName>
+ * 3. GETCOLUMN : get<ColumnName>Column
+ * where <ColumnName> is the name of the column that we are interested in.
+ */
+ final String name = method.getName();
+ if (name.startsWith("set")) {
+ return new SetData<>(method, accessorName(name.substring(3)), tableName);
+ }
+ if (name.startsWith("get")) {
+ if (name.endsWith("Row")) {
+ return GetRow.INSTANCE;
+ }
+ final String tail = name.substring(3);
+ if (tail.endsWith("Column")) {
+ return new GetColumn<>(method, tableName, accessorName(tail.substring(0, tail.length() - 6)));
+ }
+ return new GetData<>(method, accessorName(tail), tableName);
+ }
+
+ return null;
+ }
+
+ private static String accessorName(final String columnName) {
+ return columnName.toLowerCase(Locale.ROOT);
+ }
+}
--- /dev/null
+/*
+ * Copyright © 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * 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
+ */
+package org.opendaylight.ovsdb.lib.schema.typed;
+
+import static java.util.Objects.requireNonNull;
+
+import java.lang.reflect.Method;
+import org.eclipse.jdt.annotation.NonNull;
+import org.opendaylight.ovsdb.lib.error.TyperException;
+import org.opendaylight.ovsdb.lib.notation.Column;
+import org.opendaylight.ovsdb.lib.notation.Row;
+import org.opendaylight.ovsdb.lib.schema.ColumnSchema;
+import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
+
+final class SetData<T> extends MethodDispatch.ColumnPrototype<T> {
+ private static final class Invoker<T> extends MethodDispatch.ColumnInvoker<T> {
+ private final @NonNull String columnName;
+
+ Invoker(final @NonNull GenericTableSchema tableSchema, final @NonNull String columnName,
+ final ColumnSchema<GenericTableSchema, T> columnSchema) {
+ super(tableSchema, columnSchema);
+ this.columnName = requireNonNull(columnName);
+ }
+
+ @Override
+ Object invokeMethod(final Object proxy, final Object [] args) {
+ throw new UnsupportedOperationException("No backing row supplied");
+ }
+
+ @Override
+ Object invokeRowMethod(final Row<GenericTableSchema> row, final Object proxy, final Object[] args) {
+ row.addColumn(columnName, new Column<>(columnSchema(), (T) args[0]));
+ return proxy;
+ }
+ }
+
+ SetData(final Method method, final String tableName, final String columnName) {
+ super(method, findTarget(method), tableName, columnName);
+ }
+
+ @Override
+ Invoker<T> bindToImpl(final GenericTableSchema tableSchema) {
+ return new Invoker<>(tableSchema, columnName(), findColumnSchema(tableSchema));
+ }
+
+ private static Class<?> findTarget(final Method method) {
+ final Class<?>[] paramTypes = method.getParameterTypes();
+ if (paramTypes.length != 1) {
+ throw new TyperException("Setter method : " + method.getName() + " requires 1 argument");
+ }
+ return paramTypes[0];
+ }
+}
import static java.util.Objects.requireNonNull;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
import com.google.common.reflect.Reflection;
import java.util.HashMap;
import java.util.Map;
import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
final class TypedDatabaseSchemaImpl extends ForwardingDatabaseSchema implements TypedDatabaseSchema {
+ private final LoadingCache<Class<?>, TypedRowInvocationHandler> handlers = CacheBuilder.newBuilder()
+ .weakKeys().weakValues().build(new CacheLoader<Class<?>, TypedRowInvocationHandler>() {
+ @Override
+ public TypedRowInvocationHandler load(final Class<?> key) {
+ return MethodDispatch.forTarget(key).bindToSchema(TypedDatabaseSchemaImpl.this);
+ }
+ });
+
private final DatabaseSchema delegate;
TypedDatabaseSchemaImpl(final DatabaseSchema delegate) {
@Override
public GenericTableSchema getTableSchema(final Class<?> klazz) {
- return table(TypedReflections.getTableName(klazz), GenericTableSchema.class);
+ return getTableSchema(TypedReflections.getTableName(klazz));
+ }
+
+ private GenericTableSchema getTableSchema(final String tableName) {
+ return table(tableName, GenericTableSchema.class);
}
@Override
}
TyperUtils.checkVersion(getVersion(), TypedReflections.getTableVersionRange(klazz));
+ TypedRowInvocationHandler handler = handlers.getUnchecked(klazz);
if (row != null) {
- row.setTableSchema(getTableSchema(klazz));
+ row.setTableSchema(getTableSchema(handler.getTableName()));
+ handler = handler.bindToRow(row);
}
- return Reflection.newProxy(klazz, new TypedRowInvocationHandler(klazz, this, row));
+ return Reflection.newProxy(klazz, handler);
}
@Override
import static java.util.Objects.requireNonNull;
+import com.google.common.collect.ImmutableMap;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
-import java.util.Locale;
import java.util.Objects;
-import org.opendaylight.ovsdb.lib.error.ColumnSchemaNotFoundException;
-import org.opendaylight.ovsdb.lib.error.TableSchemaNotFoundException;
-import org.opendaylight.ovsdb.lib.error.TyperException;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
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.schema.ColumnSchema;
-import org.opendaylight.ovsdb.lib.schema.DatabaseSchema;
import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
+import org.opendaylight.ovsdb.lib.schema.typed.MethodDispatch.Invoker;
+/*
+ * Theory of operation: we have a set of Invoker, which are indexed by method and point to implementations we should
+ * be invoking. This mapping is data-invariant, end hence we allow rebiding to a different row (which may not be null).
+ */
final class TypedRowInvocationHandler implements InvocationHandler {
- private static final String GET_STARTS_WITH = "get";
- private static final String SET_STARTS_WITH = "set";
- private static final String GETCOLUMN_ENDS_WITH = "Column";
- private static final String GETROW_ENDS_WITH = "Row";
- private final Class<?> target;
- private final DatabaseSchema dbSchema;
- private final Row<GenericTableSchema> row;
+ private final @NonNull ImmutableMap<Method, Invoker> invokers;
+ private final @NonNull String tableName;
+ private final @Nullable Row<GenericTableSchema> row;
- TypedRowInvocationHandler(final Class<?> target, final DatabaseSchema dbSchema,
- final Row<GenericTableSchema> row) {
- this.target = requireNonNull(target);
- this.dbSchema = requireNonNull(dbSchema);
+ private TypedRowInvocationHandler(final @NonNull String tableName,
+ @NonNull final ImmutableMap<Method, Invoker> invokers, final Row<GenericTableSchema> row) {
+ this.tableName = requireNonNull(tableName);
+ this.invokers = requireNonNull(invokers);
this.row = row;
}
- private Object processGetData(final Method method) {
- String columnName = getColumnName(method);
- checkColumnSchemaVersion(dbSchema, method);
- if (columnName == null) {
- throw new TyperException("Error processing Getter : " + method.getName());
- }
- GenericTableSchema tableSchema = TyperUtils.getTableSchema(dbSchema, target);
- if (tableSchema == null) {
- throw new TableSchemaNotFoundException(TypedReflections.getTableName(target), dbSchema.getName());
- }
- ColumnSchema<GenericTableSchema, Object> columnSchema =
- TyperUtils.getColumnSchema(tableSchema, columnName, (Class<Object>) method.getReturnType());
- if (columnSchema == null) {
- throw new ColumnSchemaNotFoundException(columnName, tableSchema.getName());
- }
- if (row == null || row.getColumn(columnSchema) == null) {
- return null;
- }
- return row.getColumn(columnSchema).getData();
- }
-
- private Object processGetRow() {
- return row;
- }
-
- private Object processGetColumn(final Method method) {
- String columnName = getColumnName(method);
- checkColumnSchemaVersion(dbSchema, method);
- if (columnName == null) {
- throw new TyperException("Error processing GetColumn : " + method.getName());
- }
- GenericTableSchema tableSchema = TyperUtils.getTableSchema(dbSchema, target);
- if (tableSchema == null) {
- throw new TableSchemaNotFoundException(TypedReflections.getTableName(target), dbSchema.getName());
- }
- ColumnSchema<GenericTableSchema, Object> columnSchema =
- TyperUtils.getColumnSchema(tableSchema, columnName, (Class<Object>) method.getReturnType());
- if (columnSchema == null) {
- throw new ColumnSchemaNotFoundException(columnName, tableSchema.getName());
- }
- // When the row is null, that might indicate that the user maybe interested
- // only in the ColumnSchema and not on the Data.
- if (row == null) {
- return new Column<>(columnSchema, null);
- }
- return row.getColumn(columnSchema);
- }
-
- private Object processSetData(final Object proxy, final Method method, final Object[] args) {
- if (args == null || args.length != 1) {
- throw new TyperException("Setter method : " + method.getName() + " requires 1 argument");
- }
- checkColumnSchemaVersion(dbSchema, method);
- String columnName = getColumnName(method);
- if (columnName == null) {
- throw new TyperException("Unable to locate Column Name for " + method.getName());
- }
- GenericTableSchema tableSchema = TyperUtils.getTableSchema(dbSchema, target);
- ColumnSchema<GenericTableSchema, Object> columnSchema =
- TyperUtils.getColumnSchema(tableSchema, columnName, (Class<Object>) args[0].getClass());
- Column<GenericTableSchema, Object> column =
- new Column<>(columnSchema, args[0]);
- row.addColumn(columnName, column);
- return proxy;
- }
-
- private GenericTableSchema processGetTableSchema() {
- return TyperUtils.getTableSchema(dbSchema, target);
- }
-
- private static Boolean isHashCodeMethod(final Method method, final Object[] args) {
- return (args == null || args.length == 0) && method.getName().equals("hashCode");
+ TypedRowInvocationHandler(final @NonNull String tableName, @NonNull final ImmutableMap<Method, Invoker> invokers) {
+ this(tableName, invokers, null);
}
- private static Boolean isEqualsMethod(final Method method, final Object[] args) {
- return args != null
- && args.length == 1
- && method.getName().equals("equals")
- && Object.class.equals(method.getParameterTypes()[0]);
+ TypedRowInvocationHandler bindToRow(final @Nullable Row<GenericTableSchema> newRow) {
+ return row == newRow ? this : new TypedRowInvocationHandler(tableName, invokers, newRow);
}
- private static Boolean isToStringMethod(final Method method, final Object[] args) {
- return (args == null || args.length == 0) && method.getName().equals("toString");
+ String getTableName() {
+ return tableName;
}
@Override
- public Object invoke(final Object proxy, final Method method, final Object[] args) throws Exception {
- if (isGetTableSchema(method)) {
- return processGetTableSchema();
- } else if (isGetRow(method)) {
- return processGetRow();
- } else if (isSetData(method)) {
- return processSetData(proxy, method, args);
- } else if (isGetData(method)) {
- return processGetData(method);
- } else if (isGetColumn(method)) {
- return processGetColumn(method);
- } else if (isHashCodeMethod(method, args)) {
- return processHashCode();
- } else if (isEqualsMethod(method, args)) {
- return proxy.getClass().isInstance(args[0]) && processEquals(args[0]);
- } else if (isToStringMethod(method, args)) {
- return processToString();
+ public Object invoke(final Object proxy, final Method method, final Object[] args) {
+ final Invoker invoker = invokers.get(method);
+ return invoker != null ? invoker.invokeMethod(row, proxy, args) : invokeObjectMethod(proxy, method, args);
+ }
+
+ // Split out to aid inlining
+ private Object invokeObjectMethod(final Object proxy, final Method method, final Object[] args) {
+ switch (method.getName()) {
+ case "hashCode":
+ if (args == null || args.length == 0) {
+ return row == null ? 0 : row.hashCode();
+ }
+ break;
+ case "equals":
+ if (args != null && args.length == 1 && method.getParameterTypes()[0] == Object.class) {
+ // We only run equality or our proxy and only when it is proxying a TypedBaseTable
+ final Object obj = args[0];
+ return proxy == obj || proxy.getClass().isInstance(obj) && obj instanceof TypedBaseTable
+ && Objects.equals(row, ((TypedBaseTable<?>)obj).getRow());
+ }
+ break;
+ case "toString":
+ if (args == null || args.length == 0) {
+ return row == null ? tableName : tableName + " : " + row.toString();
+ }
+ break;
+ default:
+ break;
}
- throw new UnsupportedMethodException("Method not supported " + method.toString());
- }
-
- private boolean processEquals(final Object obj) {
- return obj instanceof TypedBaseTable && Objects.equals(row, ((TypedBaseTable<?>)obj).getRow());
- }
-
- private int processHashCode() {
- return row == null ? 0 : row.hashCode();
- }
- private String processToString() {
- final GenericTableSchema schema = processGetTableSchema();
- final String tableName = schema != null ? schema.getName() : "";
- return row == null ? tableName : tableName + " : " + row.toString();
- }
-
- private static boolean isGetColumn(final Method method) {
- TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
- if (typedColumn != null) {
- return typedColumn.method().equals(MethodType.GETCOLUMN);
- }
-
- return method.getName().startsWith(GET_STARTS_WITH) && method.getName().endsWith(GETCOLUMN_ENDS_WITH);
- }
-
- private static boolean isGetData(final Method method) {
- TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
- if (typedColumn != null) {
- return typedColumn.method().equals(MethodType.GETDATA);
- }
-
- return method.getName().startsWith(GET_STARTS_WITH) && !method.getName().endsWith(GETCOLUMN_ENDS_WITH);
- }
-
- private static boolean isGetRow(final Method method) {
- TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
- if (typedColumn != null) {
- return typedColumn.method().equals(MethodType.GETROW);
- }
-
- return method.getName().startsWith(GET_STARTS_WITH) && method.getName().endsWith(GETROW_ENDS_WITH);
- }
-
- private static boolean isGetTableSchema(final Method method) {
- TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
- return typedColumn != null && typedColumn.method().equals(MethodType.GETTABLESCHEMA);
- }
-
- private static boolean isSetData(final Method method) {
- TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
- if (typedColumn != null) {
- return typedColumn.method().equals(MethodType.SETDATA);
- }
-
- return method.getName().startsWith(SET_STARTS_WITH);
- }
-
- private static void checkColumnSchemaVersion(final DatabaseSchema dbSchema, final Method method) {
- TyperUtils.checkVersion(dbSchema.getVersion(), TypedReflections.getColumnVersionRange(method));
- }
-
- private static String getColumnName(final Method method) {
- TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
- if (typedColumn != null) {
- return typedColumn.name();
- }
-
- /*
- * Attempting to get the column name by parsing the method name with a following convention :
- * 1. GETDATA : get<ColumnName>
- * 2. SETDATA : set<ColumnName>
- * 3. GETCOLUMN : get<ColumnName>Column
- * where <ColumnName> is the name of the column that we are interested in.
- */
- int index = GET_STARTS_WITH.length();
- if (isGetData(method) || isSetData(method)) {
- return method.getName().substring(index, method.getName().length()).toLowerCase(Locale.ROOT);
- } else if (isGetColumn(method)) {
- return method.getName().substring(index, method.getName().indexOf(GETCOLUMN_ENDS_WITH,
- index)).toLowerCase(Locale.ROOT);
- }
-
- return null;
+ throw new UnsupportedMethodException("Method not supported " + method.toString());
}
}
import org.opendaylight.ovsdb.lib.notation.Row;
import org.opendaylight.ovsdb.lib.notation.UUID;
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;
return getTyped(dbSchema).getTableSchema(klazz);
}
- static ColumnSchema<GenericTableSchema, Object> getColumnSchema(final GenericTableSchema tableSchema,
- final String columnName, final Class<Object> metaClass) {
- return tableSchema.column(columnName, metaClass);
- }
-
static void checkVersion(final Version schemaVersion, final Range<Version> range) {
if (!range.contains(schemaVersion)) {
- throw new SchemaVersionMismatchException(schemaVersion,
- range.hasLowerBound() ? range.lowerEndpoint() : Version.NULL,
- range.hasUpperBound() ? range.upperEndpoint() : Version.NULL);
+ throw new SchemaVersionMismatchException(schemaVersion, range);
}
}