Skip to content

Commit e28fdb9

Browse files
refactor: slightly optimize ConstructorConstructor (#2950)
* refactor: slightly optimize ConstructorConstructor * Update comments Co-authored-by: Marcono1234 <[email protected]> * spotless apply Co-authored-by: Marcono1234 <[email protected]> --------- Co-authored-by: Marcono1234 <[email protected]>
1 parent a271f48 commit e28fdb9

File tree

1 file changed

+66
-48
lines changed

1 file changed

+66
-48
lines changed

gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java

Lines changed: 66 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,14 @@ public <T> ObjectConstructor<T> get(TypeToken<T> typeToken, boolean allowUnsafe)
109109
@SuppressWarnings("unchecked") // types must agree
110110
InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
111111
if (typeCreator != null) {
112-
return () -> typeCreator.createInstance(type);
112+
return new InstanceCreatorConstructor<>(typeCreator, type);
113113
}
114114

115115
// Next try raw type match for instance creators
116116
@SuppressWarnings("unchecked") // types must agree
117117
InstanceCreator<T> rawTypeCreator = (InstanceCreator<T>) instanceCreators.get(rawType);
118118
if (rawTypeCreator != null) {
119-
return () -> rawTypeCreator.createInstance(type);
119+
return new InstanceCreatorConstructor<>(rawTypeCreator, type);
120120
}
121121

122122
// First consider special constructors before checking for no-args constructors
@@ -143,19 +143,15 @@ public <T> ObjectConstructor<T> get(TypeToken<T> typeToken, boolean allowUnsafe)
143143
// of adjusting filter suggested below is irrelevant since it would not solve the problem
144144
String exceptionMessage = checkInstantiable(rawType);
145145
if (exceptionMessage != null) {
146-
return () -> {
147-
throw new JsonIOException(exceptionMessage);
148-
};
146+
return new ThrowingObjectConstructor<>(exceptionMessage);
149147
}
150148

151149
if (!allowUnsafe) {
152150
String message =
153151
"Unable to create instance of "
154152
+ rawType
155153
+ "; Register an InstanceCreator or a TypeAdapter for this type.";
156-
return () -> {
157-
throw new JsonIOException(message);
158-
};
154+
return new ThrowingObjectConstructor<>(message);
159155
}
160156

161157
// Consider usage of Unsafe as reflection, so don't use if BLOCK_ALL
@@ -167,9 +163,7 @@ public <T> ObjectConstructor<T> get(TypeToken<T> typeToken, boolean allowUnsafe)
167163
+ "; ReflectionAccessFilter does not permit using reflection or Unsafe. Register an"
168164
+ " InstanceCreator or a TypeAdapter for this type or adjust the access filter to"
169165
+ " allow using reflection.";
170-
return () -> {
171-
throw new JsonIOException(message);
172-
};
166+
return new ThrowingObjectConstructor<>(message);
173167
}
174168

175169
// finally try unsafe
@@ -191,10 +185,10 @@ private static <T> ObjectConstructor<T> newSpecialCollectionConstructor(
191185
T set = (T) EnumSet.noneOf((Class) elementType);
192186
return set;
193187
} else {
194-
throw new JsonIOException("Invalid EnumSet type: " + type.toString());
188+
throw new JsonIOException("Invalid EnumSet type: " + type);
195189
}
196190
} else {
197-
throw new JsonIOException("Invalid EnumSet type: " + type.toString());
191+
throw new JsonIOException("Invalid EnumSet type: " + type);
198192
}
199193
};
200194
}
@@ -209,10 +203,10 @@ else if (rawType == EnumMap.class) {
209203
T map = (T) new EnumMap((Class) elementType);
210204
return map;
211205
} else {
212-
throw new JsonIOException("Invalid EnumMap type: " + type.toString());
206+
throw new JsonIOException("Invalid EnumMap type: " + type);
213207
}
214208
} else {
215-
throw new JsonIOException("Invalid EnumMap type: " + type.toString());
209+
throw new JsonIOException("Invalid EnumMap type: " + type);
216210
}
217211
};
218212
}
@@ -250,30 +244,15 @@ private static <T> ObjectConstructor<T> newDefaultConstructor(
250244
+ " constructor is not accessible and ReflectionAccessFilter does not permit making"
251245
+ " it accessible. Register an InstanceCreator or a TypeAdapter for this type, change"
252246
+ " the visibility of the constructor or adjust the access filter.";
253-
return () -> {
254-
throw new JsonIOException(message);
255-
};
247+
return new ThrowingObjectConstructor<>(message);
256248
}
257249

258250
// Only try to make accessible if allowed; in all other cases checks above should
259251
// have verified that constructor is accessible
260252
if (filterResult == FilterResult.ALLOW) {
261253
String exceptionMessage = ReflectionHelper.tryMakeAccessible(constructor);
262254
if (exceptionMessage != null) {
263-
/*
264-
* Create ObjectConstructor which throws exception.
265-
* This keeps backward compatibility (compared to returning `null` which
266-
* would then choose another way of creating object).
267-
* And it supports types which are only serialized but not deserialized
268-
* (compared to directly throwing exception here), e.g. when runtime type
269-
* of object is inaccessible, but compile-time type is accessible.
270-
*/
271-
return () -> {
272-
// New exception is created every time to avoid keeping reference
273-
// to exception with potentially long stack trace, causing a
274-
// memory leak
275-
throw new JsonIOException(exceptionMessage);
276-
};
255+
return new ThrowingObjectConstructor<>(exceptionMessage);
277256
}
278257
}
279258

@@ -333,24 +312,24 @@ private static <T> ObjectConstructor<T> newDefaultImplementationConstructor(
333312
return null;
334313
}
335314

336-
private static ObjectConstructor<? extends Collection<? extends Object>> newCollectionConstructor(
315+
private static ObjectConstructor<? extends Collection<?>> newCollectionConstructor(
337316
Class<?> rawType) {
338317

339318
// First try List implementation
340319
if (rawType.isAssignableFrom(ArrayList.class)) {
341-
return () -> new ArrayList<>();
320+
return ArrayList::new;
342321
}
343322
// Then try Set implementation
344323
else if (rawType.isAssignableFrom(LinkedHashSet.class)) {
345-
return () -> new LinkedHashSet<>();
324+
return LinkedHashSet::new;
346325
}
347326
// Then try SortedSet / NavigableSet implementation
348327
else if (rawType.isAssignableFrom(TreeSet.class)) {
349-
return () -> new TreeSet<>();
328+
return TreeSet::new;
350329
}
351330
// Then try Queue implementation
352331
else if (rawType.isAssignableFrom(ArrayDeque.class)) {
353-
return () -> new ArrayDeque<>();
332+
return ArrayDeque::new;
354333
}
355334

356335
// Was unable to create matching Collection constructor
@@ -370,29 +349,32 @@ private static boolean hasStringKeyType(Type mapType) {
370349
return GsonTypes.getRawType(typeArguments[0]) == String.class;
371350
}
372351

373-
private static ObjectConstructor<? extends Map<? extends Object, Object>> newMapConstructor(
352+
private static ObjectConstructor<? extends Map<?, Object>> newMapConstructor(
374353
Type type, Class<?> rawType) {
375354
// First try Map implementation
376355
/*
377356
* Legacy special casing for Map<String, ...> to avoid DoS from colliding String hashCode
378357
* values for older JDKs; use own LinkedTreeMap<String, Object> instead
379358
*/
380359
if (rawType.isAssignableFrom(LinkedTreeMap.class) && hasStringKeyType(type)) {
360+
// Must use lambda instead of method reference (`LinkedTreeMap::new`) here, otherwise this
361+
// causes an exception when Gson is used by a custom system class loader, see
362+
// https://github.com/google/gson/pull/2864#issuecomment-3528623716
381363
return () -> new LinkedTreeMap<>();
382364
} else if (rawType.isAssignableFrom(LinkedHashMap.class)) {
383-
return () -> new LinkedHashMap<>();
365+
return LinkedHashMap::new;
384366
}
385367
// Then try SortedMap / NavigableMap implementation
386368
else if (rawType.isAssignableFrom(TreeMap.class)) {
387-
return () -> new TreeMap<>();
369+
return TreeMap::new;
388370
}
389371
// Then try ConcurrentMap implementation
390372
else if (rawType.isAssignableFrom(ConcurrentHashMap.class)) {
391-
return () -> new ConcurrentHashMap<>();
373+
return ConcurrentHashMap::new;
392374
}
393375
// Then try ConcurrentNavigableMap implementation
394376
else if (rawType.isAssignableFrom(ConcurrentSkipListMap.class)) {
395-
return () -> new ConcurrentSkipListMap<>();
377+
return ConcurrentSkipListMap::new;
396378
}
397379

398380
// Was unable to create matching Map constructor
@@ -431,17 +413,53 @@ private <T> ObjectConstructor<T> newUnsafeAllocator(Class<? super T> rawType) {
431413
" Or adjust your R8 configuration to keep the no-args constructor of the class.";
432414
}
433415

434-
// Separate effectively final variable to allow usage in the lambda below
435-
String exceptionMessageF = exceptionMessage;
436-
437-
return () -> {
438-
throw new JsonIOException(exceptionMessageF);
439-
};
416+
return new ThrowingObjectConstructor<>(exceptionMessage);
440417
}
441418
}
442419

443420
@Override
444421
public String toString() {
445422
return instanceCreators.toString();
446423
}
424+
425+
/**
426+
* {@link ObjectConstructor} which always throws an exception.
427+
*
428+
* <p>This keeps backward compatibility, compared to using a {@code null} {@code
429+
* ObjectConstructor}, which would then choose another way of creating the object. And it supports
430+
* types which are only serialized but not deserialized (compared to directly throwing an
431+
* exception when the {@code ObjectConstructor} is requested), e.g. when the runtime type of an
432+
* object is inaccessible, but the compile-time type is accessible.
433+
*/
434+
private static final class ThrowingObjectConstructor<T> implements ObjectConstructor<T> {
435+
private final String exceptionMessage;
436+
437+
ThrowingObjectConstructor(String exceptionMessage) {
438+
this.exceptionMessage = exceptionMessage;
439+
}
440+
441+
@Override
442+
public T construct() {
443+
// New exception is created every time to avoid keeping a reference to an exception with
444+
// potentially long stack trace, causing a memory leak
445+
// (which would happen if the exception was already created when the
446+
// `ExceptionObjectConstructor` is created)
447+
throw new JsonIOException(exceptionMessage);
448+
}
449+
}
450+
451+
private static final class InstanceCreatorConstructor<T> implements ObjectConstructor<T> {
452+
private final InstanceCreator<T> instanceCreator;
453+
private final Type type;
454+
455+
InstanceCreatorConstructor(InstanceCreator<T> instanceCreator, Type type) {
456+
this.instanceCreator = instanceCreator;
457+
this.type = type;
458+
}
459+
460+
@Override
461+
public T construct() {
462+
return instanceCreator.createInstance(type);
463+
}
464+
}
447465
}

0 commit comments

Comments
 (0)