Skip to content

Method: BeanClassProcessor()

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