001package co.codewizards.cloudstore.ls.core.invoke;
002
003import static co.codewizards.cloudstore.core.util.Util.*;
004import static java.util.Objects.*;
005
006import java.util.Collections;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.LinkedHashSet;
010import java.util.Map;
011import java.util.Set;
012
013import org.slf4j.Logger;
014import org.slf4j.LoggerFactory;
015
016import co.codewizards.cloudstore.core.Uid;
017
018public class ClassManager {
019        private static final Logger logger = LoggerFactory.getLogger(ClassManager.class);
020
021        private final Uid clientId;
022
023        // classes on the side of the real objects (not the proxies!)
024        private Map<Integer, Class<?>> classId2Class = new HashMap<>();
025        private Map<Class<?>, Integer> class2ClassId = new HashMap<>();
026        private Set<Integer> classIdsKnownByRemoteSide = new HashSet<>();
027
028        // class-info on the side of the real objects (not the proxies!)
029        // the ClassInfoMap is used on the proxies' side.
030        private Map<Integer, ClassInfo> classId2ClassInfo = new HashMap<Integer, ClassInfo>();
031
032        private static final Map<String, Class<?>> primitiveClassName2Class;
033        static {
034                final Class<?>[] primitives = {
035                                byte.class,
036                                short.class,
037                                int.class,
038                                long.class,
039                                float.class,
040                                double.class,
041                                char.class,
042                                boolean.class
043                };
044
045                final Map<String, Class<?>> m = new HashMap<>(primitives.length);
046
047                for (Class<?> clazz : primitives)
048                        m.put(clazz.getName(), clazz);
049
050                primitiveClassName2Class = Collections.unmodifiableMap(m);
051        }
052//      private static Set<Class<?>> primitiveClasses = Collections.unmodifiableSet(new HashSet<Class<?>>(primitiveClassName2Class.values()));
053//
054//      public static Set<Class<?>> getPrimitiveClasses() {
055//              return primitiveClasses;
056//      }
057
058        public ClassManager(final Uid clientId) {
059                this.clientId = requireNonNull(clientId, "clientId");
060                logger.debug("[{}].<init>: Created ClassManager.", clientId);
061        }
062
063        private int nextClassId;
064
065        public synchronized int getClassIdOrFail(final Class<?> clazz) {
066                final int classId = getClassId(clazz);
067                if (classId < 0)
068                        throw new IllegalArgumentException(String.format("ClassManager[%s] does not have classId for this class: %s",
069                                        clientId, clazz.getName()));
070
071                return classId;
072        }
073
074        public synchronized int getClassId(final Class<?> clazz) {
075                requireNonNull(clazz, "clazz");
076                Integer classId = class2ClassId.get(clazz);
077                if (classId == null)
078                        return -1;
079                else
080                        return classId;
081        }
082
083        public synchronized int getClassIdOrCreate(final Class<?> clazz) {
084                requireNonNull(clazz, "clazz");
085                Integer classId = class2ClassId.get(clazz);
086                if (classId == null) {
087                        classId = nextClassId();
088                        logger.debug("[{}].getClassIdOrCreate: Assigned classId={} to {}.", clientId, classId, clazz.getName());
089                        class2ClassId.put(clazz, classId);
090                        classId2Class.put(classId, clazz);
091                }
092                return classId;
093        }
094
095        public synchronized Class<?> getClassOrFail(final int classId) {
096                final Class<?> clazz = getClass(classId);
097                if (clazz == null)
098                        throw new IllegalArgumentException(String.format("ClassManager[%s] does not have class for this classId: %s",
099                                        clientId, classId));
100
101                return clazz;
102        }
103
104        public synchronized Class<?> getClass(final int classId) {
105                final Class<?> clazz = classId2Class.get(classId);
106                return clazz;
107        }
108
109        public synchronized boolean isClassIdKnownByRemoteSide(int classId) {
110                final boolean result = classIdsKnownByRemoteSide.contains(classId);
111                return result;
112        }
113
114        public synchronized void setClassIdKnownByRemoteSide(int classId) {
115                if (classIdsKnownByRemoteSide.add(classId))
116                        logger.debug("[{}].setClassIdKnownByRemoteSide: classId={}", clientId, classId);
117        }
118
119        public synchronized ClassInfo getClassInfo(int classId) {
120                ClassInfo classInfo = classId2ClassInfo.get(classId);
121                if (classInfo == null) {
122                        final Class<?> clazz = getClass(classId);
123
124                        if (clazz == null)
125                                return null;
126
127                        final Set<String> interfaceNames = getInterfaceNames(clazz);
128                        classInfo = new ClassInfo(classId, clazz.getName(), interfaceNames, isEqualsOverridden(clazz));
129                        classId2ClassInfo.put(classId, classInfo);
130                }
131                return classInfo;
132        }
133
134        private boolean isEqualsOverridden(final Class<?> clazz) {
135                Class<?> c = clazz;
136                while (c != Object.class) {
137                        try {
138                                c.getDeclaredMethod("equals", Object.class);
139                                return true;
140                        } catch (NoSuchMethodException | SecurityException e) {
141                                doNothing();
142                        }
143                        c = c.getSuperclass();
144                }
145                return false;
146        }
147
148        protected synchronized int nextClassId() {
149                return nextClassId++;
150        }
151
152        protected Set<String> getInterfaceNames(Class<?> clazz) {
153                requireNonNull(clazz, "clazz");
154                final Set<String> interfaceNames = new LinkedHashSet<>();
155                populateInterfaceNames(interfaceNames, clazz);
156                return interfaceNames;
157        }
158
159        private void populateInterfaceNames(Set<String> interfaceNames, Class<?> clazz) {
160                if (clazz.isInterface())
161                        interfaceNames.add(clazz.getName());
162
163                for (Class<?> iface : clazz.getInterfaces())
164                        populateInterfaceNames(interfaceNames, iface);
165
166                final Class<?> superclass = clazz.getSuperclass();
167                if (superclass != Object.class && superclass != null)
168                        populateInterfaceNames(interfaceNames, superclass);
169        }
170
171        public Class<?>[] getClassesOrFail(final String[] classNames) {
172                requireNonNull(classNames, "classNames");
173                final Class<?>[] classes = new Class<?>[classNames.length];
174
175                for (int i = 0; i < classNames.length; i++)
176                        classes[i] = getClassOrFail(classNames[i]);
177
178                return classes;
179        }
180
181        public Class<?> getClassOrFail(final String className) {
182                requireNonNull(className, "className");
183
184                Class<?> clazz = primitiveClassName2Class.get(className);
185                if (clazz != null)
186                        return clazz;
187
188                // TODO maybe use context-class-loader, too and other loaders (which?)?
189                try {
190                        clazz = Class.forName(className);
191                } catch (ClassNotFoundException e) {
192                        throw new IllegalArgumentException(e);
193                }
194                return clazz;
195        }
196}