-
Notifications
You must be signed in to change notification settings - Fork 2
/
BeanClassProcessor.java
258 lines (240 loc) · 9.49 KB
/
BeanClassProcessor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/**
* Copyright (C) 2020 Czech Technical University in Prague
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details. You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.cvut.kbss.jsonld.common;
import cz.cvut.kbss.jsonld.exception.BeanProcessingException;
import cz.cvut.kbss.jsonld.exception.TargetTypeException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URL;
import java.util.*;
public class BeanClassProcessor {
private BeanClassProcessor() {
throw new AssertionError();
}
/**
* Extracts value of the specified field, from the specified instance.
*
* @param field The field to extract value from
* @param instance Instance containing the field
* @return Field value, possibly {@code null}
*/
public static Object getFieldValue(Field field, Object instance) {
Objects.requireNonNull(field);
if (!field.isAccessible()) {
field.setAccessible(true);
}
try {
return field.get(instance);
} catch (IllegalAccessException e) {
throw new BeanProcessingException("Unable to extract value of field " + field, e);
}
}
/**
* Sets value of the specified field.
*
* @param field The field to set
* @param instance Instance on which the field will be set
* @param value The value to use
*/
public static void setFieldValue(Field field, Object instance, Object value) {
Objects.requireNonNull(field);
if (!field.isAccessible()) {
field.setAccessible(true);
}
try {
field.set(instance, value);
} catch (IllegalAccessException e) {
throw new BeanProcessingException("Unable to set value of field " + field, e);
}
}
/**
* Creates new instance of the specified class.
*
* @param cls The class to instantiate
* @return New instance
* @throws BeanProcessingException If the class is missing a public no-arg constructor
*/
public static <T> T createInstance(Class<T> cls) {
try {
return cls.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new BeanProcessingException("Class " + cls + " is missing a public no-arg constructor.", e);
}
}
/**
* Creates collection of the specified type.
*
* @param type Collection type
* @return New collection instance
*/
public static Collection<?> createCollection(CollectionType type) {
switch (type) {
case LIST:
return new ArrayList<>();
case SET:
return new HashSet<>();
default:
throw new IllegalArgumentException("Unsupported collection type " + type);
}
}
/**
* Creates a collection to fill the specified field.
* <p>
* I.e. the type of the collection is determined from the declared type of the field.
*
* @param field The field to create collection for
* @return New collection instance
*/
public static Collection<?> createCollection(Field field) {
Objects.requireNonNull(field);
return createCollection(field.getType());
}
/**
* Creates an instance of the specified collection type.
* <p>
* Lists and sets are supported.
*
* @param collectionType Type of the collection
* @return New collection instance
*/
public static Collection<?> createCollection(Class<?> collectionType) {
Objects.requireNonNull(collectionType);
CollectionType type;
if (Set.class.isAssignableFrom(collectionType)) {
type = CollectionType.SET;
} else if (List.class.isAssignableFrom(collectionType)) {
type = CollectionType.LIST;
} else {
throw new IllegalArgumentException(collectionType + " is not a supported collection type.");
}
return createCollection(type);
}
/**
* Determines the declared element type of collection represented by the specified field.
*
* @param field Field whose type is a collection
* @return Declared element type of the collection
*/
public static Class<?> getCollectionItemType(Field field) {
Objects.requireNonNull(field);
return getGenericType(field, 0);
}
private static Class<?> getGenericType(Field field, int paramIndex) {
try {
final ParameterizedType fieldType = (ParameterizedType) field.getGenericType();
final Type typeArgument = fieldType.getActualTypeArguments()[paramIndex];
if (typeArgument instanceof Class) {
return (Class<?>) typeArgument;
} else {
return (Class<?>) ((ParameterizedType) typeArgument).getRawType();
}
} catch (ClassCastException e) {
throw new BeanProcessingException("Field " + field + " is not of parametrized type.");
}
}
/**
* Determines the declared type of keys of the map represented by the specified field.
*
* @param field Map field
* @return Declared type of values
*/
public static Class<?> getMapKeyType(Field field) {
return getGenericType(field, 0);
}
/**
* Gets the declared type of values of the map represented by the specified field.
*
* @param field Map field
* @return Declared type of values
*/
public static Class<?> getMapValueType(Field field) {
Objects.requireNonNull(field);
try {
return getGenericType(field, 1);
} catch (ArrayIndexOutOfBoundsException e) {
throw new BeanProcessingException("Unable to determine declared Map value type of field " + field + ".", e);
}
}
/**
* In case the map represent by the specified field has as value another generic type, this method retrieves this
* generic type's actual first argument.
* <p>
* This implementation is supposed to determine value type of {@link cz.cvut.kbss.jopa.model.annotations.Properties}
* fields with the following declaration {@code Map<String, Collection<?>>}, where the collection can be replaced by
* a more specific type (List, Set) and the map key type can be also different.
*
* @param field Map field
* @return Value type if present, {@code null} otherwise
*/
public static Class<?> getMapGenericValueType(Field field) {
try {
final ParameterizedType fieldType = (ParameterizedType) field.getGenericType();
final Type typeArgument = fieldType.getActualTypeArguments()[1];
if (typeArgument instanceof Class) {
if (Collection.class.isAssignableFrom((Class<?>) typeArgument)) {
// For raw value type - Map<?, Collection>
return null;
}
throw new BeanProcessingException("Expected map value type to be generic. Field: " + field);
} else {
final ParameterizedType valueType = (ParameterizedType) typeArgument;
final Type actualType = valueType.getActualTypeArguments()[0];
if (Class.class.isAssignableFrom(actualType.getClass())) {
// For Map<?, Collection<String>>
return Class.class.cast(actualType);
}
// For Map<?, Collection<?>>
return null;
}
} catch (ClassCastException e) {
throw new BeanProcessingException("Field " + field + " is not of parametrized type.");
}
}
/**
* Checks whether the specified field represents a collection.
*
* @param field The field to examine
* @return Whether the field is a collection or not
*/
public static boolean isCollection(Field field) {
Objects.requireNonNull(field);
return Collection.class.isAssignableFrom(field.getType());
}
/**
* Checks that the properties field is a {@link Map}.
*
* @param field The field to check
* @throws cz.cvut.kbss.jsonld.exception.TargetTypeException When the field is not a Map
*/
public static void verifyPropertiesFieldType(Field field) {
if (!Map.class.isAssignableFrom(field.getType())) {
throw new TargetTypeException("@Properties field " + field + " must be a java.util.Map.");
}
}
/**
* Checks whether the specified type is a valid identifier type.
* <p>
* Valid identifiers in JOPA are: {@link URI}, {@link URL}, and {@link String}.
*
* @param cls Class to check
* @return {@code true} if the specified class can be used as identifier field type, {@code false} otherwise
*/
public static boolean isIdentifierType(Class<?> cls) {
// TODO This should be in JOPA API and reused from there
return URI.class.equals(cls) || URL.class.equals(cls) || String.class.equals(cls);
}
}