Skip to content

Method: isCollection(Field)

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