f8b96a6d10db4a131ab8d9cc2a08b57f196d093f
[ovsdb.git] / library / impl / src / main / java / org / opendaylight / ovsdb / lib / schema / typed / TypedRowInvocationHandler.java
1 /*
2  * Copyright © 2014, 2017 Red Hat, Inc. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.ovsdb.lib.schema.typed;
9
10 import static java.util.Objects.requireNonNull;
11
12 import java.lang.reflect.InvocationHandler;
13 import java.lang.reflect.Method;
14 import java.util.Locale;
15 import java.util.Objects;
16 import org.opendaylight.ovsdb.lib.error.ColumnSchemaNotFoundException;
17 import org.opendaylight.ovsdb.lib.error.TableSchemaNotFoundException;
18 import org.opendaylight.ovsdb.lib.error.TyperException;
19 import org.opendaylight.ovsdb.lib.error.UnsupportedMethodException;
20 import org.opendaylight.ovsdb.lib.notation.Column;
21 import org.opendaylight.ovsdb.lib.notation.Row;
22 import org.opendaylight.ovsdb.lib.schema.ColumnSchema;
23 import org.opendaylight.ovsdb.lib.schema.DatabaseSchema;
24 import org.opendaylight.ovsdb.lib.schema.GenericTableSchema;
25
26 final class TypedRowInvocationHandler implements InvocationHandler {
27     private static final String GET_STARTS_WITH = "get";
28     private static final String SET_STARTS_WITH = "set";
29     private static final String GETCOLUMN_ENDS_WITH = "Column";
30     private static final String GETROW_ENDS_WITH = "Row";
31
32     private final Class<?> target;
33     private final DatabaseSchema dbSchema;
34     private final Row<GenericTableSchema> row;
35
36     TypedRowInvocationHandler(final Class<?> target, final DatabaseSchema dbSchema,
37             final Row<GenericTableSchema> row) {
38         this.target = requireNonNull(target);
39         this.dbSchema = requireNonNull(dbSchema);
40         this.row = row;
41     }
42
43     private Object processGetData(final Method method) {
44         String columnName = getColumnName(method);
45         checkColumnSchemaVersion(dbSchema, method);
46         if (columnName == null) {
47             throw new TyperException("Error processing Getter : " + method.getName());
48         }
49         GenericTableSchema tableSchema = TyperUtils.getTableSchema(dbSchema, target);
50         if (tableSchema == null) {
51             throw new TableSchemaNotFoundException(TypedReflections.getTableName(target), dbSchema.getName());
52         }
53         ColumnSchema<GenericTableSchema, Object> columnSchema =
54                 TyperUtils.getColumnSchema(tableSchema, columnName, (Class<Object>) method.getReturnType());
55         if (columnSchema == null) {
56             throw new ColumnSchemaNotFoundException(columnName, tableSchema.getName());
57         }
58         if (row == null || row.getColumn(columnSchema) == null) {
59             return null;
60         }
61         return row.getColumn(columnSchema).getData();
62     }
63
64     private Object processGetRow() {
65         return row;
66     }
67
68     private Object processGetColumn(final Method method) {
69         String columnName = getColumnName(method);
70         checkColumnSchemaVersion(dbSchema, method);
71         if (columnName == null) {
72             throw new TyperException("Error processing GetColumn : " + method.getName());
73         }
74         GenericTableSchema tableSchema = TyperUtils.getTableSchema(dbSchema, target);
75         if (tableSchema == null) {
76             throw new TableSchemaNotFoundException(TypedReflections.getTableName(target), dbSchema.getName());
77         }
78         ColumnSchema<GenericTableSchema, Object> columnSchema =
79                 TyperUtils.getColumnSchema(tableSchema, columnName, (Class<Object>) method.getReturnType());
80         if (columnSchema == null) {
81             throw new ColumnSchemaNotFoundException(columnName, tableSchema.getName());
82         }
83         // When the row is null, that might indicate that the user maybe interested
84         // only in the ColumnSchema and not on the Data.
85         if (row == null) {
86             return new Column<>(columnSchema, null);
87         }
88         return row.getColumn(columnSchema);
89     }
90
91     private Object processSetData(final Object proxy, final Method method, final Object[] args) {
92         if (args == null || args.length != 1) {
93             throw new TyperException("Setter method : " + method.getName() + " requires 1 argument");
94         }
95         checkColumnSchemaVersion(dbSchema, method);
96         String columnName = getColumnName(method);
97         if (columnName == null) {
98             throw new TyperException("Unable to locate Column Name for " + method.getName());
99         }
100         GenericTableSchema tableSchema = TyperUtils.getTableSchema(dbSchema, target);
101         ColumnSchema<GenericTableSchema, Object> columnSchema =
102                 TyperUtils.getColumnSchema(tableSchema, columnName, (Class<Object>) args[0].getClass());
103         Column<GenericTableSchema, Object> column =
104                 new Column<>(columnSchema, args[0]);
105         row.addColumn(columnName, column);
106         return proxy;
107     }
108
109     private GenericTableSchema processGetTableSchema() {
110         return TyperUtils.getTableSchema(dbSchema, target);
111     }
112
113     private static Boolean isHashCodeMethod(final Method method, final Object[] args) {
114         return (args == null || args.length == 0) && method.getName().equals("hashCode");
115     }
116
117     private static Boolean isEqualsMethod(final Method method, final Object[] args) {
118         return args != null
119                 && args.length == 1
120                 && method.getName().equals("equals")
121                 && Object.class.equals(method.getParameterTypes()[0]);
122     }
123
124     private static Boolean isToStringMethod(final Method method, final Object[] args) {
125         return (args == null || args.length == 0) && method.getName().equals("toString");
126     }
127
128     @Override
129     public Object invoke(final Object proxy, final Method method, final Object[] args) throws Exception {
130         if (isGetTableSchema(method)) {
131             return processGetTableSchema();
132         } else if (isGetRow(method)) {
133             return processGetRow();
134         } else if (isSetData(method)) {
135             return processSetData(proxy, method, args);
136         } else if (isGetData(method)) {
137             return processGetData(method);
138         } else if (isGetColumn(method)) {
139             return processGetColumn(method);
140         } else if (isHashCodeMethod(method, args)) {
141             return processHashCode();
142         } else if (isEqualsMethod(method, args)) {
143             return proxy.getClass().isInstance(args[0]) && processEquals(args[0]);
144         } else if (isToStringMethod(method, args)) {
145             return processToString();
146         }
147         throw new UnsupportedMethodException("Method not supported " + method.toString());
148     }
149
150     private boolean processEquals(final Object obj) {
151         return obj instanceof TypedBaseTable && Objects.equals(row, ((TypedBaseTable<?>)obj).getRow());
152     }
153
154     private int processHashCode() {
155         return row == null ? 0 : row.hashCode();
156     }
157
158     private String processToString() {
159         final GenericTableSchema schema = processGetTableSchema();
160         final String tableName = schema != null ? schema.getName() : "";
161         return row == null ? tableName : tableName + " : " + row.toString();
162     }
163
164     private static boolean isGetColumn(final Method method) {
165         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
166         if (typedColumn != null) {
167             return typedColumn.method().equals(MethodType.GETCOLUMN);
168         }
169
170         return method.getName().startsWith(GET_STARTS_WITH) && method.getName().endsWith(GETCOLUMN_ENDS_WITH);
171     }
172
173     private static boolean isGetData(final Method method) {
174         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
175         if (typedColumn != null) {
176             return typedColumn.method().equals(MethodType.GETDATA);
177         }
178
179         return method.getName().startsWith(GET_STARTS_WITH) && !method.getName().endsWith(GETCOLUMN_ENDS_WITH);
180     }
181
182     private static boolean isGetRow(final Method method) {
183         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
184         if (typedColumn != null) {
185             return typedColumn.method().equals(MethodType.GETROW);
186         }
187
188         return method.getName().startsWith(GET_STARTS_WITH) && method.getName().endsWith(GETROW_ENDS_WITH);
189     }
190
191     private static boolean isGetTableSchema(final Method method) {
192         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
193         return typedColumn != null && typedColumn.method().equals(MethodType.GETTABLESCHEMA);
194     }
195
196     private static boolean isSetData(final Method method) {
197         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
198         if (typedColumn != null) {
199             return typedColumn.method().equals(MethodType.SETDATA);
200         }
201
202         return method.getName().startsWith(SET_STARTS_WITH);
203     }
204
205     private static void checkColumnSchemaVersion(final DatabaseSchema dbSchema, final Method method) {
206         TyperUtils.checkVersion(dbSchema.getVersion(), TypedReflections.getColumnVersionRange(method));
207     }
208
209     private static String getColumnName(final Method method) {
210         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
211         if (typedColumn != null) {
212             return typedColumn.name();
213         }
214
215         /*
216          * Attempting to get the column name by parsing the method name with a following convention :
217          * 1. GETDATA : get<ColumnName>
218          * 2. SETDATA : set<ColumnName>
219          * 3. GETCOLUMN : get<ColumnName>Column
220          * where <ColumnName> is the name of the column that we are interested in.
221          */
222         int index = GET_STARTS_WITH.length();
223         if (isGetData(method) || isSetData(method)) {
224             return method.getName().substring(index, method.getName().length()).toLowerCase(Locale.ROOT);
225         } else if (isGetColumn(method)) {
226             return method.getName().substring(index, method.getName().indexOf(GETCOLUMN_ENDS_WITH,
227                     index)).toLowerCase(Locale.ROOT);
228         }
229
230         return null;
231     }
232 }