/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Granite Data Services is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.messaging.reflect;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.granite.messaging.annotations.Exclude;
import org.granite.messaging.annotations.Include;
import org.granite.messaging.annotations.Serialized;
/**
* @author Franck WOLFF
*/
public class ClassDescriptor {
private static final Class<?>[] WRITE_OBJECT_PARAMS = new Class<?>[]{ ObjectOutputStream.class };
private static final Class<?>[] READ_OBJECT_PARAMS = new Class<?>[]{ ObjectInputStream.class };
private final Class<?> cls;
private final Instantiator instantiator;
private final List<Property> properties;
private final Method writeObjectMethod;
private final Method readObjectMethod;
private final Method writeReplaceMethod;
private final Method readResolveMethod;
private final ClassDescriptor parent;
private volatile SoftReference<List<Property>> inheritedProperties = new SoftReference<List<Property>>(null);
public ClassDescriptor(Reflection reflection, Class<?> cls) {
this.cls = cls;
this.instantiator = getInstantiator(reflection, cls);
if (!Externalizable.class.isAssignableFrom(cls)) {
this.writeObjectMethod = getPrivateMethod(cls, "writeObject", WRITE_OBJECT_PARAMS, Void.TYPE);
this.readObjectMethod = getPrivateMethod(cls, "readObject", READ_OBJECT_PARAMS, Void.TYPE);
}
else {
this.writeObjectMethod = null;
this.readObjectMethod = null;
}
this.writeReplaceMethod = getInheritedMethod(cls, "writeReplace", null, Object.class);
this.readResolveMethod = getInheritedMethod(cls, "readResolve", null, Object.class);
this.properties = getSerializableProperties(reflection, cls);
this.parent = reflection.getDescriptor(cls.getSuperclass());
}
private static Instantiator getInstantiator(Reflection reflection, Class<?> cls) {
try {
return new ConstructorInstantiator(cls.getConstructor());
}
catch (NoSuchMethodException e) {
// Fallback...
}
try {
return reflection.instanceFactory.newInstantiator(cls);
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public Class<?> getCls() {
return cls;
}
public Object newInstance() throws InstantiationException, IllegalAccessException, InvocationTargetException {
return instantiator.newInstance();
}
public List<Property> getSerializableProperties() {
return properties;
}
public List<Property> getInheritedSerializableProperties() {
List<Property> inheritedSerializableProperties = inheritedProperties.get();
if (inheritedSerializableProperties == null) {
if (parent == null)
inheritedSerializableProperties = properties;
else {
List<Property> parentProperties = parent.getInheritedSerializableProperties();
if (!parentProperties.isEmpty()) {
if (!properties.isEmpty()) {
inheritedSerializableProperties = new ArrayList<Property>(parentProperties.size() + properties.size());
inheritedSerializableProperties.addAll(parentProperties);
inheritedSerializableProperties.addAll(properties);
inheritedSerializableProperties = Collections.unmodifiableList(inheritedSerializableProperties);
}
else
inheritedSerializableProperties = parentProperties;
}
else
inheritedSerializableProperties = properties;
}
inheritedProperties = new SoftReference<List<Property>>(inheritedSerializableProperties);
}
return inheritedSerializableProperties;
}
public boolean hasWriteObjectMethod() {
return writeObjectMethod != null;
}
public void invokeWriteObjectMethod(ObjectOutputStream oos, Object v) throws IOException {
if (writeObjectMethod == null)
throw new UnsupportedOperationException("No writeObject(...) method in " + cls);
try {
writeObjectMethod.invoke(v, oos);
}
catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof IOException)
throw (IOException)t;
throw new IOException(t);
}
catch (IllegalAccessException e) {
throw new InternalError();
}
}
public boolean hasReadObjectMethod() {
return readObjectMethod != null;
}
public void invokeReadObjectMethod(ObjectInputStream ois, Object v) throws ClassNotFoundException, IOException {
if (readObjectMethod == null)
throw new UnsupportedOperationException("No readObject(...) method in " + cls);
try {
readObjectMethod.invoke(v, ois);
}
catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof ClassNotFoundException)
throw (ClassNotFoundException)t;
if (t instanceof IOException)
throw (IOException)t;
throw new IOException(t);
}
catch (IllegalAccessException e) {
throw new InternalError();
}
}
public boolean hasWriteReplaceMethod() {
return writeReplaceMethod != null;
}
public Object invokeWriteReplaceMethod(Object v) throws IOException {
if (writeReplaceMethod == null)
throw new UnsupportedOperationException("No writeReplace() method in " + cls);
try {
return writeReplaceMethod.invoke(v);
}
catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof IOException)
throw (IOException)t;
throw new IOException(t);
}
catch (IllegalAccessException e) {
throw new InternalError();
}
}
public boolean hasReadResolveMethod() {
return readResolveMethod != null;
}
public Object invokeReadResolveMethod(Object v) throws IOException {
if (readResolveMethod == null)
throw new UnsupportedOperationException("No readResolve() method in " + cls);
try {
return readResolveMethod.invoke(v);
}
catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof IOException)
throw (IOException)t;
throw new IOException(t);
}
catch (IllegalAccessException e) {
throw new InternalError();
}
}
public ClassDescriptor getParent() {
return parent;
}
protected static Method getInheritedMethod(Class<?> cls, String name, Class<?>[] params, Class<?> ret) {
try {
Method method = cls.getDeclaredMethod(name, params);
method.setAccessible(true);
return (
method.getReturnType() == ret && ((Modifier.STATIC | Modifier.ABSTRACT) & method.getModifiers()) == 0 ?
method :
null
);
}
catch (NoSuchMethodException e) {
}
final Class<?> root = cls;
while ((cls = cls.getSuperclass()) != null) {
try {
Method method = cls.getDeclaredMethod(name, params);
method.setAccessible(true);
if (method.getReturnType() != ret)
return null;
final int modifiers = method.getModifiers();
if (((Modifier.STATIC | Modifier.ABSTRACT | Modifier.PRIVATE) & modifiers) != 0)
return null;
if (((Modifier.PUBLIC | Modifier.PROTECTED) & modifiers) != 0)
return method;
return root.getPackage() == cls.getPackage() ? method : null;
}
catch (NoSuchMethodException e) {
}
}
return null;
}
protected static Method getPrivateMethod(Class<?> cls, String name, Class<?>[] params, Class<?> ret) {
try {
Method method = cls.getDeclaredMethod(name, params);
method.setAccessible(true);
if (method.getReturnType() == ret && (method.getModifiers() & (Modifier.STATIC | Modifier.PRIVATE)) == Modifier.PRIVATE)
return method;
}
catch (NoSuchMethodException e) {
}
return null;
}
protected static List<Property> getSerializableProperties(Reflection reflection, Class<?> cls) {
Field[] declaredFields = cls.getDeclaredFields();
List<Property> serializableProperties = new ArrayList<Property>(declaredFields.length);
for (Field field : declaredFields) {
if ((field.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) == 0 &&
!field.isAnnotationPresent(Exclude.class)) {
field.setAccessible(true);
serializableProperties.add(reflection.newFieldProperty(field));
}
}
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method method : declaredMethods) {
if ((method.getModifiers() & (Modifier.STATIC | Modifier.PRIVATE | Modifier.PROTECTED)) == 0 &&
method.getReturnType() != Void.TYPE &&
method.isAnnotationPresent(Include.class) &&
method.getParameterTypes().length == 0) {
String name = method.getName();
if (name.startsWith("get")) {
if (name.length() <= 3)
continue;
name = name.substring(3, 4).toLowerCase() + name.substring(4);
}
else if (name.startsWith("is") &&
(method.getReturnType() == Boolean.class || method.getReturnType() == Boolean.TYPE)) {
if (name.length() <= 2)
continue;
name = name.substring(2, 3).toLowerCase() + name.substring(3);
}
else
continue;
serializableProperties.add(reflection.newMethodProperty(method, null, name));
}
}
Serialized serialized = cls.getAnnotation(Serialized.class);
if (serialized != null && serialized.propertiesOrder().length > 0) {
String[] value = serialized.propertiesOrder();
if (value.length != serializableProperties.size())
throw new ReflectionException("Illegal @Serialized(propertiesOrder) value: " + serialized + " on: " + cls.getName() + " (bad length)");
for (int i = 0; i < value.length; i++) {
String propertyName = value[i];
boolean found = false;
for (int j = i; j < value.length; j++) {
Property property = serializableProperties.get(j);
if (property.getName().equals(propertyName)) {
found = true;
if (i != j) {
serializableProperties.set(j, serializableProperties.get(i));
serializableProperties.set(i, property);
}
break;
}
}
if (!found)
throw new ReflectionException("Illegal @Serialized(propertiesOrder) value: " + serialized + " on: " + cls.getName() + " (\"" + propertyName + "\" isn't a property name)");
}
}
else
Collections.sort(serializableProperties, reflection.getLexicalPropertyComparator());
return Collections.unmodifiableList(serializableProperties);
}
}