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