ParseApplication/ExternalLibs/Parse-SDK-Android/Parse/src/main/java/com/parse/ParseObjectSubclassingContr...

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();
}
}