package co.codewizards.cloudstore.ls.core.invoke;
import static co.codewizards.cloudstore.core.util.AssertUtil.*;
import static co.codewizards.cloudstore.core.util.Util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import co.codewizards.cloudstore.core.Uid;
public class ClassManager {
private static final Logger logger = LoggerFactory.getLogger(ClassManager.class);
private final Uid clientId;
// classes on the side of the real objects (not the proxies!)
private Map<Integer, Class<?>> classId2Class = new HashMap<>();
private Map<Class<?>, Integer> class2ClassId = new HashMap<>();
private Set<Integer> classIdsKnownByRemoteSide = new HashSet<>();
// class-info on the side of the real objects (not the proxies!)
// the ClassInfoMap is used on the proxies' side.
private Map<Integer, ClassInfo> classId2ClassInfo = new HashMap<Integer, ClassInfo>();
private static final Map<String, Class<?>> primitiveClassName2Class;
static {
final Class<?>[] primitives = {
byte.class,
short.class,
int.class,
long.class,
float.class,
double.class,
char.class,
boolean.class
};
final Map<String, Class<?>> m = new HashMap<>(primitives.length);
for (Class<?> clazz : primitives)
m.put(clazz.getName(), clazz);
primitiveClassName2Class = Collections.unmodifiableMap(m);
}
// private static Set<Class<?>> primitiveClasses = Collections.unmodifiableSet(new HashSet<Class<?>>(primitiveClassName2Class.values()));
//
// public static Set<Class<?>> getPrimitiveClasses() {
// return primitiveClasses;
// }
public ClassManager(final Uid clientId) {
this.clientId = assertNotNull(clientId, "clientId");
logger.debug("[{}].<init>: Created ClassManager.", clientId);
}
private int nextClassId;
public synchronized int getClassIdOrFail(final Class<?> clazz) {
final int classId = getClassId(clazz);
if (classId < 0)
throw new IllegalArgumentException(String.format("ClassManager[%s] does not have classId for this class: %s",
clientId, clazz.getName()));
return classId;
}
public synchronized int getClassId(final Class<?> clazz) {
assertNotNull(clazz, "clazz");
Integer classId = class2ClassId.get(clazz);
if (classId == null)
return -1;
else
return classId;
}
public synchronized int getClassIdOrCreate(final Class<?> clazz) {
assertNotNull(clazz, "clazz");
Integer classId = class2ClassId.get(clazz);
if (classId == null) {
classId = nextClassId();
logger.debug("[{}].getClassIdOrCreate: Assigned classId={} to {}.", clientId, classId, clazz.getName());
class2ClassId.put(clazz, classId);
classId2Class.put(classId, clazz);
}
return classId;
}
public synchronized Class<?> getClassOrFail(final int classId) {
final Class<?> clazz = getClass(classId);
if (clazz == null)
throw new IllegalArgumentException(String.format("ClassManager[%s] does not have class for this classId: %s",
clientId, classId));
return clazz;
}
public synchronized Class<?> getClass(final int classId) {
final Class<?> clazz = classId2Class.get(classId);
return clazz;
}
public synchronized boolean isClassIdKnownByRemoteSide(int classId) {
final boolean result = classIdsKnownByRemoteSide.contains(classId);
return result;
}
public synchronized void setClassIdKnownByRemoteSide(int classId) {
if (classIdsKnownByRemoteSide.add(classId))
logger.debug("[{}].setClassIdKnownByRemoteSide: classId={}", clientId, classId);
}
public synchronized ClassInfo getClassInfo(int classId) {
ClassInfo classInfo = classId2ClassInfo.get(classId);
if (classInfo == null) {
final Class<?> clazz = getClass(classId);
if (clazz == null)
return null;
final Set<String> interfaceNames = getInterfaceNames(clazz);
classInfo = new ClassInfo(classId, clazz.getName(), interfaceNames, isEqualsOverridden(clazz));
classId2ClassInfo.put(classId, classInfo);
}
return classInfo;
}
private boolean isEqualsOverridden(final Class<?> clazz) {
Class<?> c = clazz;
while (c != Object.class) {
try {
c.getDeclaredMethod("equals", Object.class);
return true;
} catch (NoSuchMethodException | SecurityException e) {
doNothing();
}
c = c.getSuperclass();
}
return false;
}
protected synchronized int nextClassId() {
return nextClassId++;
}
protected Set<String> getInterfaceNames(Class<?> clazz) {
assertNotNull(clazz, "clazz");
final Set<String> interfaceNames = new LinkedHashSet<>();
populateInterfaceNames(interfaceNames, clazz);
return interfaceNames;
}
private void populateInterfaceNames(Set<String> interfaceNames, Class<?> clazz) {
if (clazz.isInterface())
interfaceNames.add(clazz.getName());
for (Class<?> iface : clazz.getInterfaces())
populateInterfaceNames(interfaceNames, iface);
final Class<?> superclass = clazz.getSuperclass();
if (superclass != Object.class && superclass != null)
populateInterfaceNames(interfaceNames, superclass);
}
public Class<?>[] getClassesOrFail(final String[] classNames) {
assertNotNull(classNames, "classNames");
final Class<?>[] classes = new Class<?>[classNames.length];
for (int i = 0; i < classNames.length; i++)
classes[i] = getClassOrFail(classNames[i]);
return classes;
}
public Class<?> getClassOrFail(final String className) {
assertNotNull(className, "className");
Class<?> clazz = primitiveClassName2Class.get(className);
if (clazz != null)
return clazz;
// TODO maybe use context-class-loader, too and other loaders (which?)?
try {
clazz = Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
return clazz;
}
}