Extract TypedRowInvocationHandler
[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             String message = TableSchemaNotFoundException.createMessage(TypedReflections.getTableName(target),
52                         dbSchema.getName());
53             throw new TableSchemaNotFoundException(message);
54         }
55         ColumnSchema<GenericTableSchema, Object> columnSchema =
56                 TyperUtils.getColumnSchema(tableSchema, columnName, (Class<Object>) method.getReturnType());
57         if (columnSchema == null) {
58             String message = ColumnSchemaNotFoundException.createMessage(columnName, tableSchema.getName());
59             throw new ColumnSchemaNotFoundException(message);
60         }
61         if (row == null || row.getColumn(columnSchema) == null) {
62             return null;
63         }
64         return row.getColumn(columnSchema).getData();
65     }
66
67     private Object processGetRow() {
68         return row;
69     }
70
71     private Object processGetColumn(final Method method) {
72         String columnName = getColumnName(method);
73         checkColumnSchemaVersion(dbSchema, method);
74         if (columnName == null) {
75             throw new TyperException("Error processing GetColumn : " + method.getName());
76         }
77         GenericTableSchema tableSchema = TyperUtils.getTableSchema(dbSchema, target);
78         if (tableSchema == null) {
79             String message = TableSchemaNotFoundException.createMessage(TypedReflections.getTableName(target),
80                 dbSchema.getName());
81             throw new TableSchemaNotFoundException(message);
82         }
83         ColumnSchema<GenericTableSchema, Object> columnSchema =
84                 TyperUtils.getColumnSchema(tableSchema, columnName, (Class<Object>) method.getReturnType());
85         if (columnSchema == null) {
86             String message = ColumnSchemaNotFoundException.createMessage(columnName, tableSchema.getName());
87             throw new ColumnSchemaNotFoundException(message);
88         }
89         // When the row is null, that might indicate that the user maybe interested
90         // only in the ColumnSchema and not on the Data.
91         if (row == null) {
92             return new Column<>(columnSchema, null);
93         }
94         return row.getColumn(columnSchema);
95     }
96
97     private Object processSetData(final Object proxy, final Method method, final Object[] args) {
98         if (args == null || args.length != 1) {
99             throw new TyperException("Setter method : " + method.getName() + " requires 1 argument");
100         }
101         checkColumnSchemaVersion(dbSchema, method);
102         String columnName = getColumnName(method);
103         if (columnName == null) {
104             throw new TyperException("Unable to locate Column Name for " + method.getName());
105         }
106         GenericTableSchema tableSchema = TyperUtils.getTableSchema(dbSchema, target);
107         ColumnSchema<GenericTableSchema, Object> columnSchema =
108                 TyperUtils.getColumnSchema(tableSchema, columnName, (Class<Object>) args[0].getClass());
109         Column<GenericTableSchema, Object> column =
110                 new Column<>(columnSchema, args[0]);
111         row.addColumn(columnName, column);
112         return proxy;
113     }
114
115     private GenericTableSchema processGetTableSchema() {
116         return TyperUtils.getTableSchema(dbSchema, target);
117     }
118
119     private static Boolean isHashCodeMethod(final Method method, final Object[] args) {
120         return (args == null || args.length == 0) && method.getName().equals("hashCode");
121     }
122
123     private static Boolean isEqualsMethod(final Method method, final Object[] args) {
124         return args != null
125                 && args.length == 1
126                 && method.getName().equals("equals")
127                 && Object.class.equals(method.getParameterTypes()[0]);
128     }
129
130     private static Boolean isToStringMethod(final Method method, final Object[] args) {
131         return (args == null || args.length == 0) && method.getName().equals("toString");
132     }
133
134     @Override
135     public Object invoke(final Object proxy, final Method method, final Object[] args) throws Exception {
136         if (isGetTableSchema(method)) {
137             return processGetTableSchema();
138         } else if (isGetRow(method)) {
139             return processGetRow();
140         } else if (isSetData(method)) {
141             return processSetData(proxy, method, args);
142         } else if (isGetData(method)) {
143             return processGetData(method);
144         } else if (isGetColumn(method)) {
145             return processGetColumn(method);
146         } else if (isHashCodeMethod(method, args)) {
147             return processHashCode();
148         } else if (isEqualsMethod(method, args)) {
149             return proxy.getClass().isInstance(args[0]) && processEquals(args[0]);
150         } else if (isToStringMethod(method, args)) {
151             return processToString();
152         }
153         throw new UnsupportedMethodException("Method not supported " + method.toString());
154     }
155
156     private boolean processEquals(final Object obj) {
157         return obj instanceof TypedBaseTable && Objects.equals(row, ((TypedBaseTable<?>)obj).getRow());
158     }
159
160     private int processHashCode() {
161         return row == null ? 0 : row.hashCode();
162     }
163
164     private String processToString() {
165         final GenericTableSchema schema = processGetTableSchema();
166         final String tableName = schema != null ? schema.getName() : "";
167         return row == null ? tableName : tableName + " : " + row.toString();
168     }
169
170     private static boolean isGetColumn(final Method method) {
171         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
172         if (typedColumn != null) {
173             return typedColumn.method().equals(MethodType.GETCOLUMN);
174         }
175
176         return method.getName().startsWith(GET_STARTS_WITH) && method.getName().endsWith(GETCOLUMN_ENDS_WITH);
177     }
178
179     private static boolean isGetData(final Method method) {
180         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
181         if (typedColumn != null) {
182             return typedColumn.method().equals(MethodType.GETDATA);
183         }
184
185         return method.getName().startsWith(GET_STARTS_WITH) && !method.getName().endsWith(GETCOLUMN_ENDS_WITH);
186     }
187
188     private static boolean isGetRow(final Method method) {
189         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
190         if (typedColumn != null) {
191             return typedColumn.method().equals(MethodType.GETROW);
192         }
193
194         return method.getName().startsWith(GET_STARTS_WITH) && method.getName().endsWith(GETROW_ENDS_WITH);
195     }
196
197     private static boolean isGetTableSchema(final Method method) {
198         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
199         return typedColumn != null && typedColumn.method().equals(MethodType.GETTABLESCHEMA);
200     }
201
202     private static boolean isSetData(final Method method) {
203         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
204         if (typedColumn != null) {
205             return typedColumn.method().equals(MethodType.SETDATA);
206         }
207
208         return method.getName().startsWith(SET_STARTS_WITH);
209     }
210
211     private static void checkColumnSchemaVersion(final DatabaseSchema dbSchema, final Method method) {
212         TyperUtils.checkVersion(dbSchema.getVersion(), TypedReflections.getColumnVersionRange(method));
213     }
214
215     private static String getColumnName(final Method method) {
216         TypedColumn typedColumn = method.getAnnotation(TypedColumn.class);
217         if (typedColumn != null) {
218             return typedColumn.name();
219         }
220
221         /*
222          * Attempting to get the column name by parsing the method name with a following convention :
223          * 1. GETDATA : get<ColumnName>
224          * 2. SETDATA : set<ColumnName>
225          * 3. GETCOLUMN : get<ColumnName>Column
226          * where <ColumnName> is the name of the column that we are interested in.
227          */
228         int index = GET_STARTS_WITH.length();
229         if (isGetData(method) || isSetData(method)) {
230             return method.getName().substring(index, method.getName().length()).toLowerCase(Locale.ROOT);
231         } else if (isGetColumn(method)) {
232             return method.getName().substring(index, method.getName().indexOf(GETCOLUMN_ENDS_WITH,
233                     index)).toLowerCase(Locale.ROOT);
234         }
235
236         return null;
237     }
238 }