131 lines
4.8 KiB
Java
131 lines
4.8 KiB
Java
/*
|
|
* Copyright (c) 2015-present, Parse, LLC.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
package com.parse;
|
|
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.Modifier;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
/* package */ class ParseObjectSubclassingController {
|
|
private final Object mutex = new Object();
|
|
private final Map<String, Constructor<? extends ParseObject>> registeredSubclasses = new HashMap<>();
|
|
|
|
/* package */ String getClassName(Class<? extends ParseObject> clazz) {
|
|
ParseClassName info = clazz.getAnnotation(ParseClassName.class);
|
|
if (info == null) {
|
|
throw new IllegalArgumentException("No ParseClassName annotation provided on " + clazz);
|
|
}
|
|
return info.value();
|
|
}
|
|
|
|
/* package */ boolean isSubclassValid(String className, Class<? extends ParseObject> clazz) {
|
|
Constructor<? extends ParseObject> constructor = null;
|
|
|
|
synchronized (mutex) {
|
|
constructor = registeredSubclasses.get(className);
|
|
}
|
|
|
|
return constructor == null
|
|
? clazz == ParseObject.class
|
|
: constructor.getDeclaringClass() == clazz;
|
|
}
|
|
|
|
/* package */ void registerSubclass(Class<? extends ParseObject> clazz) {
|
|
if (!ParseObject.class.isAssignableFrom(clazz)) {
|
|
throw new IllegalArgumentException("Cannot register a type that is not a subclass of ParseObject");
|
|
}
|
|
|
|
String className = getClassName(clazz);
|
|
Constructor<? extends ParseObject> previousConstructor = null;
|
|
|
|
synchronized (mutex) {
|
|
previousConstructor = registeredSubclasses.get(className);
|
|
if (previousConstructor != null) {
|
|
Class<? extends ParseObject> previousClass = previousConstructor.getDeclaringClass();
|
|
if (clazz.isAssignableFrom(previousClass)) {
|
|
// Previous subclass is more specific or equal to the current type, do nothing.
|
|
return;
|
|
} else if (previousClass.isAssignableFrom(clazz)) {
|
|
// Previous subclass is parent of new child subclass, fallthrough and actually
|
|
// register this class.
|
|
/* Do nothing */
|
|
} else {
|
|
throw new IllegalArgumentException(
|
|
"Tried to register both " + previousClass.getName() + " and " + clazz.getName() +
|
|
" as the ParseObject subclass of " + className + ". " + "Cannot determine the right " +
|
|
"class to use because neither inherits from the other."
|
|
);
|
|
}
|
|
}
|
|
|
|
try {
|
|
registeredSubclasses.put(className, getConstructor(clazz));
|
|
} catch (NoSuchMethodException ex) {
|
|
throw new IllegalArgumentException(
|
|
"Cannot register a type that does not implement the default constructor!"
|
|
);
|
|
} catch (IllegalAccessException ex) {
|
|
throw new IllegalArgumentException(
|
|
"Default constructor for " + clazz + " is not accessible."
|
|
);
|
|
}
|
|
}
|
|
|
|
if (previousConstructor != null) {
|
|
// TODO: This is super tightly coupled. Let's remove it when automatic registration is in.
|
|
// NOTE: Perform this outside of the mutex, to prevent any potential deadlocks.
|
|
if (className.equals(getClassName(ParseUser.class))) {
|
|
ParseUser.getCurrentUserController().clearFromMemory();
|
|
} else if (className.equals(getClassName(ParseInstallation.class))) {
|
|
ParseInstallation.getCurrentInstallationController().clearFromMemory();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* package */ void unregisterSubclass(Class<? extends ParseObject> clazz) {
|
|
String className = getClassName(clazz);
|
|
|
|
synchronized (mutex) {
|
|
registeredSubclasses.remove(className);
|
|
}
|
|
}
|
|
|
|
/* package */ ParseObject newInstance(String className) {
|
|
Constructor<? extends ParseObject> constructor = null;
|
|
|
|
synchronized (mutex) {
|
|
constructor = registeredSubclasses.get(className);
|
|
}
|
|
|
|
try {
|
|
return constructor != null
|
|
? constructor.newInstance()
|
|
: new ParseObject(className);
|
|
} catch (RuntimeException e) {
|
|
throw e;
|
|
} catch (Exception e) {
|
|
throw new RuntimeException("Failed to create instance of subclass.", e);
|
|
}
|
|
}
|
|
|
|
private static Constructor<? extends ParseObject> getConstructor(Class<? extends ParseObject> clazz) throws NoSuchMethodException, IllegalAccessException {
|
|
Constructor<? extends ParseObject> constructor = clazz.getDeclaredConstructor();
|
|
if (constructor == null) {
|
|
throw new NoSuchMethodException();
|
|
}
|
|
int modifiers = constructor.getModifiers();
|
|
if (Modifier.isPublic(modifiers) || (clazz.getPackage().getName().equals("com.parse") &&
|
|
!(Modifier.isProtected(modifiers) || Modifier.isPrivate(modifiers)))) {
|
|
return constructor;
|
|
}
|
|
throw new IllegalAccessException();
|
|
}
|
|
}
|