001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 *   This file is part of the Granite Data Services Platform.
006 *
007 *   Granite Data Services is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   Granite Data Services is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 *   General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public
018 *   License along with this library; if not, write to the Free Software
019 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 *   USA, or see <http://www.gnu.org/licenses/>.
021 */
022package org.granite.messaging.reflect;
023
024import java.io.Externalizable;
025import java.io.IOException;
026import java.io.ObjectInputStream;
027import java.io.ObjectOutputStream;
028import java.lang.ref.SoftReference;
029import java.lang.reflect.Field;
030import java.lang.reflect.InvocationTargetException;
031import java.lang.reflect.Method;
032import java.lang.reflect.Modifier;
033import java.util.ArrayList;
034import java.util.Collections;
035import java.util.List;
036
037import org.granite.messaging.annotations.Exclude;
038import org.granite.messaging.annotations.Include;
039import org.granite.messaging.annotations.Serialized;
040
041/**
042 * @author Franck WOLFF
043 */
044public class ClassDescriptor {
045        
046        private static final Class<?>[] WRITE_OBJECT_PARAMS = new Class<?>[]{ ObjectOutputStream.class };
047        private static final Class<?>[] READ_OBJECT_PARAMS = new Class<?>[]{ ObjectInputStream.class };
048        
049        private final Class<?> cls;
050        private final Instantiator instantiator;
051        private final List<Property> properties;
052        private final Method writeObjectMethod;
053        private final Method readObjectMethod;
054        private final Method writeReplaceMethod;
055        private final Method readResolveMethod;
056        
057        private final ClassDescriptor parent;
058
059        private volatile SoftReference<List<Property>> inheritedProperties = new SoftReference<List<Property>>(null);
060        
061        public ClassDescriptor(Reflection reflection, Class<?> cls) {
062                this.cls = cls;
063                
064                this.instantiator = getInstantiator(reflection, cls);
065                
066                if (!Externalizable.class.isAssignableFrom(cls)) {
067                        this.writeObjectMethod = getPrivateMethod(cls, "writeObject", WRITE_OBJECT_PARAMS, Void.TYPE);
068                        this.readObjectMethod = getPrivateMethod(cls, "readObject", READ_OBJECT_PARAMS, Void.TYPE);
069                }
070                else {
071                        this.writeObjectMethod = null;
072                        this.readObjectMethod = null;
073                }
074                
075                this.writeReplaceMethod = getInheritedMethod(cls, "writeReplace", null, Object.class);
076                this.readResolveMethod = getInheritedMethod(cls, "readResolve", null, Object.class);
077                
078                this.properties = getSerializableProperties(reflection, cls);
079
080                this.parent = reflection.getDescriptor(cls.getSuperclass());
081        }
082        
083        private static Instantiator getInstantiator(Reflection reflection, Class<?> cls) {
084                try {
085                        return new ConstructorInstantiator(cls.getConstructor());
086                }
087                catch (NoSuchMethodException e) {
088                        // Fallback...
089                }
090                
091                try {
092                        return reflection.instanceFactory.newInstantiator(cls);
093                }
094                catch (IllegalAccessException e) {
095                        throw new RuntimeException(e);
096                }
097                catch (InvocationTargetException e) {
098                        throw new RuntimeException(e);
099                }
100        }
101
102        public Class<?> getCls() {
103                return cls;
104        }
105        
106        public Object newInstance() throws InstantiationException, IllegalAccessException, InvocationTargetException {
107                return instantiator.newInstance();
108        }
109
110        public List<Property> getSerializableProperties() {
111                return properties;
112        }
113        
114        public List<Property> getInheritedSerializableProperties() {
115                List<Property> inheritedSerializableProperties = inheritedProperties.get();
116                if (inheritedSerializableProperties == null) {
117                        if (parent == null)
118                                inheritedSerializableProperties = properties;
119                        else {
120                                List<Property> parentProperties = parent.getInheritedSerializableProperties();
121                                if (!parentProperties.isEmpty()) {
122                                        if (!properties.isEmpty()) {
123                                                inheritedSerializableProperties = new ArrayList<Property>(parentProperties.size() + properties.size());
124                                                inheritedSerializableProperties.addAll(parentProperties);
125                                                inheritedSerializableProperties.addAll(properties);
126                                                inheritedSerializableProperties = Collections.unmodifiableList(inheritedSerializableProperties);
127                                        }
128                                        else
129                                                inheritedSerializableProperties = parentProperties;
130                                }
131                                else
132                                        inheritedSerializableProperties = properties;
133                        }
134                        inheritedProperties = new SoftReference<List<Property>>(inheritedSerializableProperties);
135                }
136                return inheritedSerializableProperties;
137        }
138        
139        public boolean hasWriteObjectMethod() {
140                return writeObjectMethod != null;
141        }
142
143        public void invokeWriteObjectMethod(ObjectOutputStream oos, Object v) throws IOException {
144                if (writeObjectMethod == null)
145                        throw new UnsupportedOperationException("No writeObject(...) method in " + cls);
146                
147                try {
148                        writeObjectMethod.invoke(v, oos);
149                }
150                catch (InvocationTargetException e) {
151                        Throwable t = e.getTargetException();
152                        if (t instanceof IOException)
153                                throw (IOException)t;
154                        throw new IOException(t);
155                }
156                catch (IllegalAccessException e) {
157                        throw new InternalError();
158                }
159        }
160        
161        public boolean hasReadObjectMethod() {
162                return readObjectMethod != null;
163        }
164        
165        public void invokeReadObjectMethod(ObjectInputStream ois, Object v) throws ClassNotFoundException, IOException {
166                if (readObjectMethod == null)
167                        throw new UnsupportedOperationException("No readObject(...) method in " + cls);
168                
169                try {
170                        readObjectMethod.invoke(v, ois);
171                }
172                catch (InvocationTargetException e) {
173                        Throwable t = e.getTargetException();
174                        if (t instanceof ClassNotFoundException)
175                                throw (ClassNotFoundException)t;
176                        if (t instanceof IOException)
177                                throw (IOException)t;
178                        throw new IOException(t);
179                }
180                catch (IllegalAccessException e) {
181                        throw new InternalError();
182                }
183        }
184        
185        public boolean hasWriteReplaceMethod() {
186                return writeReplaceMethod != null;
187        }
188
189        public Object invokeWriteReplaceMethod(Object v) throws IOException {
190                if (writeReplaceMethod == null)
191                        throw new UnsupportedOperationException("No writeReplace() method in " + cls);
192                
193                try {
194                        return writeReplaceMethod.invoke(v);
195                }
196                catch (InvocationTargetException e) {
197                        Throwable t = e.getTargetException();
198                        if (t instanceof IOException)
199                                throw (IOException)t;
200                        throw new IOException(t);
201                }
202                catch (IllegalAccessException e) {
203                        throw new InternalError();
204                }
205        }
206
207        public boolean hasReadResolveMethod() {
208                return readResolveMethod != null;
209        }
210
211        public Object invokeReadResolveMethod(Object v) throws IOException {
212                if (readResolveMethod == null)
213                        throw new UnsupportedOperationException("No readResolve() method in " + cls);
214                
215                try {
216                        return readResolveMethod.invoke(v);
217                }
218                catch (InvocationTargetException e) {
219                        Throwable t = e.getTargetException();
220                        if (t instanceof IOException)
221                                throw (IOException)t;
222                        throw new IOException(t);
223                }
224                catch (IllegalAccessException e) {
225                        throw new InternalError();
226                }
227        }
228
229        public ClassDescriptor getParent() {
230                return parent;
231        }
232        
233        protected static Method getInheritedMethod(Class<?> cls, String name, Class<?>[] params, Class<?> ret) {
234                try {
235                        Method method = cls.getDeclaredMethod(name, params);
236                        method.setAccessible(true);
237                        return (
238                                method.getReturnType() == ret && ((Modifier.STATIC | Modifier.ABSTRACT) & method.getModifiers()) == 0 ?
239                                method :
240                                null
241                        );
242                }
243                catch (NoSuchMethodException e) {
244                }
245                
246                final Class<?> root = cls;
247                while ((cls = cls.getSuperclass()) != null) {
248                        try {
249                                Method method = cls.getDeclaredMethod(name, params);
250                                method.setAccessible(true);
251                                
252                                if (method.getReturnType() != ret)
253                                        return null;
254                                
255                                final int modifiers = method.getModifiers();
256                                
257                                if (((Modifier.STATIC | Modifier.ABSTRACT | Modifier.PRIVATE) & modifiers) != 0)
258                                        return null;
259                                
260                                if (((Modifier.PUBLIC | Modifier.PROTECTED) & modifiers) != 0)
261                                        return method;
262                                
263                                return root.getPackage() == cls.getPackage() ? method : null;
264                        }
265                        catch (NoSuchMethodException e) {
266                        }
267                }
268                
269                return null;
270        }
271        
272        protected static Method getPrivateMethod(Class<?> cls, String name, Class<?>[] params, Class<?> ret) {
273                try {
274                        Method method = cls.getDeclaredMethod(name, params);
275                        method.setAccessible(true);
276                        if (method.getReturnType() == ret && (method.getModifiers() & (Modifier.STATIC | Modifier.PRIVATE)) == Modifier.PRIVATE)
277                                return method;
278                }
279                catch (NoSuchMethodException e) {
280                }
281                return null;
282        }
283        
284        protected static List<Property> getSerializableProperties(Reflection reflection, Class<?> cls) {
285                Field[] declaredFields = cls.getDeclaredFields();
286                
287                List<Property> serializableProperties = new ArrayList<Property>(declaredFields.length);
288                for (Field field : declaredFields) {
289                        if ((field.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) == 0 &&
290                                !field.isAnnotationPresent(Exclude.class)) {
291                                field.setAccessible(true);
292                                serializableProperties.add(reflection.newFieldProperty(field));
293                        }
294                }
295                
296                Method[] declaredMethods = cls.getDeclaredMethods();
297                for (Method method : declaredMethods) {
298                        if ((method.getModifiers() & (Modifier.STATIC | Modifier.PRIVATE | Modifier.PROTECTED)) == 0 &&
299                                method.getReturnType() != Void.TYPE &&
300                                method.isAnnotationPresent(Include.class) &&
301                                method.getParameterTypes().length == 0) {
302                                
303                                String name = method.getName();
304                                if (name.startsWith("get")) {
305                                        if (name.length() <= 3)
306                                                continue;
307                                        name = name.substring(3, 4).toLowerCase() + name.substring(4);
308                                }
309                                else if (name.startsWith("is") &&
310                                        (method.getReturnType() == Boolean.class || method.getReturnType() == Boolean.TYPE)) {
311                                        if (name.length() <= 2)
312                                                continue;
313                                        name = name.substring(2, 3).toLowerCase() + name.substring(3);
314                                }
315                                else
316                                        continue;
317                                
318                                serializableProperties.add(reflection.newMethodProperty(method, null, name));
319                        }
320                }
321                
322                Serialized serialized = cls.getAnnotation(Serialized.class);
323                if (serialized != null && serialized.propertiesOrder().length > 0) {
324                        String[] value = serialized.propertiesOrder();
325                        
326                        if (value.length != serializableProperties.size())
327                                throw new ReflectionException("Illegal @Serialized(propertiesOrder) value: " + serialized + " on: " + cls.getName() + " (bad length)");
328                        
329                        for (int i = 0; i < value.length; i++) {
330                                String propertyName = value[i];
331                                
332                                boolean found = false;
333                                for (int j = i; j < value.length; j++) {
334                                        Property property = serializableProperties.get(j);
335                                        if (property.getName().equals(propertyName)) {
336                                                found = true;
337                                                if (i != j) {
338                                                        serializableProperties.set(j, serializableProperties.get(i));
339                                                        serializableProperties.set(i, property);
340                                                }
341                                                break;
342                                        }
343                                }
344                                if (!found)
345                                        throw new ReflectionException("Illegal @Serialized(propertiesOrder) value: " + serialized + " on: " + cls.getName() + " (\"" + propertyName + "\" isn't a property name)");
346                        }
347                }
348                else
349                        Collections.sort(serializableProperties, reflection.getLexicalPropertyComparator());
350                
351                return Collections.unmodifiableList(serializableProperties);
352        }
353}