All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.granite.client.persistence.Persistence Maven / Gradle / Ivy

/*
  GRANITE DATA SERVICES
  Copyright (C) 2013 GRANITE DATA SERVICES S.A.S.

  This file is part of Granite Data Services.

  Granite Data Services is free software; you can redistribute it and/or modify
  it under the terms of the GNU Library General Public License as published by
  the Free Software Foundation; either version 2 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 Library General Public License
  for more details.

  You should have received a copy of the GNU Library General Public License
  along with this library; if not, see .
*/

package org.granite.client.persistence;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.granite.client.persistence.collection.PersistentCollection;

/**
 * @author Franck WOLFF
 */
public class Persistence {

	private static final String INITIALIZED_FIELD_NAME = "__initialized__";
	private static final String DETACHED_STATE_FIELD_NAME = "__detachedState__";
	
	private static final PropertyAccessor NULL_PROPERTY_ACCESSOR = new MethodAccessor(null, null, null);
	
	private static final ConcurrentMap, PropertyAccessor> idAccessors = new ConcurrentHashMap, PropertyAccessor>();
	private static final ConcurrentMap, PropertyAccessor> uidAccessors = new ConcurrentHashMap, PropertyAccessor>();
	private static final ConcurrentMap, PropertyAccessor> versionAccessors = new ConcurrentHashMap, PropertyAccessor>();
	
	private static final Field NULL_FIELD;
	static {
		try {
			NULL_FIELD = Persistence.class.getDeclaredField("NULL_FIELD");
		}
		catch (Exception e) {
			throw new ExceptionInInitializerError(e);
		}
	}
	
	private static final ConcurrentMap, Field> initializedFields = new ConcurrentHashMap, Field>();
	private static final ConcurrentMap, Field> detachedStateFields = new ConcurrentHashMap, Field>();

	public static Field getInitializedField(Class cls) {
		return findField(cls, initializedFields, INITIALIZED_FIELD_NAME, Boolean.TYPE);
	}

	public static Field getDetachedStateField(Class cls) {
		return findField(cls, detachedStateFields, DETACHED_STATE_FIELD_NAME, String.class);
	}
	
	public static boolean isInitialized(Object o) {
		if (o instanceof PersistentCollection)
			return ((PersistentCollection)o).wasInitialized();
		
		Class cls = o.getClass();
		if (!cls.isAnnotationPresent(Entity.class))
			return true;

		Field field = findField(cls, initializedFields, INITIALIZED_FIELD_NAME, Boolean.TYPE);
		if (field == null)
			return true;
		
		try {
			return field.getBoolean(o);
		}
		catch (Exception e) {
			throw new RuntimeException("Could not get field " + field + " value on object " + o, e);
		}
	}
	
	public static void setInitialized(Object o, boolean value) throws IllegalAccessException {
		Class cls = o.getClass();
		if (!cls.isAnnotationPresent(Entity.class))
			throw new IllegalAccessException("Not annotated with @Entity: " + cls);
		
		Field field = findField(cls, initializedFields, INITIALIZED_FIELD_NAME, Boolean.TYPE);
		if (field == null)
			throw new IllegalAccessException("Could not find field " + INITIALIZED_FIELD_NAME + " in " + o);
		
		try {
			field.setBoolean(o, value);
		}
		catch (Exception e) {
			throw new RuntimeException("Could not set field " + field + " on object " + o + " to value " + value, e);
		}		
	}
	
	public static String getDetachedState(Object o) throws IllegalAccessException {
		Class cls = o.getClass();
		if (!cls.isAnnotationPresent(Entity.class))
			return null;

		Field field = findField(cls, detachedStateFields, DETACHED_STATE_FIELD_NAME, String.class);
		if (field == null)
			throw new IllegalAccessException("Could not find field " + DETACHED_STATE_FIELD_NAME + " in " + o);
		
		return (String)field.get(o);
	}
	
	public static void setDetachedState(Object o, String value) throws IllegalAccessException {
		Class cls = o.getClass();
		if (!cls.isAnnotationPresent(Entity.class))
			throw new IllegalAccessException("Not annotated with @Entity: " + cls);
		
		Field field = findField(cls, detachedStateFields, DETACHED_STATE_FIELD_NAME, String.class);
		if (field == null)
			throw new IllegalAccessException("Could not find field " + DETACHED_STATE_FIELD_NAME + " in " + o);
		
		try {
			field.set(o, value);
		}
		catch (Exception e) {
			throw new RuntimeException("Could not set field " + field + " on object " + o + " to value " + value, e);
		}		
	}
	
	@SuppressWarnings("unchecked")
	public static  T getId(Object entity) throws IllegalAccessException {
		return (T)getIdProperty(entity).getValue();
	}

	public static void setId(Object entity, Object value) throws IllegalAccessException {
		getIdProperty(entity).setValue(value);
	}
	
	public static String getUid(Object entity) throws IllegalAccessException {
		return (String)getUidProperty(entity).getValue();
	}
	
	public static void setUid(Object entity, String value) throws IllegalAccessException {
		getUidProperty(entity).setValue(value);
	}
	
	@SuppressWarnings("unchecked")
	public static  T getVersion(Object entity) throws IllegalAccessException {
		return (T)getVersionProperty(entity).getValue();
	}

	public static Property getIdProperty(Object entity) throws IllegalAccessException {
		return getProperty(entity, idAccessors, Id.class);
	}

	public static Property getUidProperty(Object entity) throws IllegalAccessException {
		return getProperty(entity, uidAccessors, Uid.class);
	}

	public static Property getVersionProperty(Object entity) throws IllegalAccessException {
		return getProperty(entity, versionAccessors, Version.class);
	}
	
	private static Property getProperty(Object entity, ConcurrentMap, PropertyAccessor> cache, Class annotationClass) throws IllegalAccessException {
		PropertyAccessor accessor = findPropertyAccessor(entity.getClass(), cache, annotationClass);
		if (accessor == null)
			throw newIllegalAccessException(entity.getClass(), annotationClass);
		return new Property(entity, accessor);
	}
	
	private static IllegalAccessException newIllegalAccessException(Class cls, Class annotationClass) {
		return new IllegalAccessException("Could not find property annotated with " + annotationClass + " in " + cls);
	}
	
	private static Field findField(Class cls, ConcurrentMap, Field> cache, String name, Class type) {
		
		Field field = cache.get(cls);
		
		if (field == null) {
			for (Class c = cls; c != null && c != Object.class; c = c.getSuperclass()) {
				try {
					field = c.getDeclaredField(name);
				}
				catch (Exception e) {
					continue;
				}
				
				if (field.getType() != type)
					continue;
				
				field.setAccessible(true);
				break;
			}
			
			if (field == null)
				field = NULL_FIELD;
			
			Field previous = cache.putIfAbsent(cls, field);
			if (previous != null)
				field = previous;
		}
		
		return (field != NULL_FIELD ? field : null);
	}
	
	private static PropertyAccessor findPropertyAccessor(Class cls, ConcurrentMap, PropertyAccessor> cache, Class annotationClass) {
		
		PropertyAccessor accessor = cache.get(cls);
		
		if (accessor == null) {
			boolean searchFields = false;
			boolean searchMethods = false;
			
			if (!annotationClass.isAnnotationPresent(Target.class))
				searchFields = searchMethods = true;
			else {
				Target target = annotationClass.getAnnotation(Target.class);
				for (ElementType targetType : target.value()) {
					if (targetType == ElementType.FIELD)
						searchFields = true;
					else if (targetType == ElementType.METHOD)
						searchMethods = true;
				}
			}
			
			if (searchFields == false && searchMethods == false)
				return null;
			
			final int modifierMask = Modifier.PUBLIC | Modifier.STATIC;
			
			classLoop:
			for (Class c = cls; c != null && c != Object.class; c = c.getSuperclass()) {
				if (searchMethods) {
					for (Method method : c.getDeclaredMethods()) {
						if ((method.getModifiers() & modifierMask) != Modifier.PUBLIC ||
							!method.isAnnotationPresent(annotationClass))
							continue;
						
						if (method.getReturnType() == Void.TYPE) {
							if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) {
								String name = method.getName().substring(3);
								
								if (name.length() == 0)
									continue;
								
								Method getter = null;
								try {
									getter = cls.getMethod("get" + name);
								}
								catch (Exception e) {
									try {
										getter = cls.getMethod("is" + name);
									}
									catch (Exception f) {
									}
								}
								
								if (getter != null && (getter.getModifiers() & Modifier.STATIC) != 0 &&
									getter.getReturnType() != method.getParameterTypes()[0])
									getter = null;
								
								accessor = new MethodAccessor(getter, method, uncapitalizePropertyName(name));
								break classLoop;
							}
						}
						else if (method.getParameterTypes().length == 0 && (method.getName().startsWith("get") || method.getName().startsWith("is"))) {
							String name;
							if (method.getName().startsWith("get"))
								name = method.getName().substring(3);
							else
								name = method.getName().substring(2);
							
							if (name.length() == 0)
								continue;
							
							Method setter = null;
							try {
								setter = cls.getMethod("set" + name);
							}
							catch (Exception e) {
							}
							
							if (setter != null && (setter.getModifiers() & Modifier.STATIC) != 0 &&
								method.getReturnType() != setter.getParameterTypes()[0])
								setter = null;
							
							accessor = new MethodAccessor(method, setter, uncapitalizePropertyName(name));
							break classLoop;
						}
					}
				}
				
				if (searchFields) {
					for (Field field : c.getDeclaredFields()) {
						if ((field.getModifiers() & Modifier.STATIC) == 0 && field.isAnnotationPresent(annotationClass)) {
							accessor = new FieldAccessor(field);
							break classLoop;
						}
					}
				}
			}
			
			if (accessor == null)
				accessor = NULL_PROPERTY_ACCESSOR;
			
			PropertyAccessor previous = cache.putIfAbsent(cls, accessor);
			if (previous != null)
				accessor = previous;
		}
		
		return (accessor != NULL_PROPERTY_ACCESSOR ? accessor : null);
	}
	
	private static String uncapitalizePropertyName(String name) {
		if (name.length() > 0 && Character.isUpperCase(name.charAt(0)))
			name = name.substring(0, 1).toLowerCase() + name.substring(1);
		return name;
	}
	
	static interface PropertyAccessor {
		
		String getName();
		Class getType();
		
		boolean isReadable();
		boolean isWritable();
		
		Object get(Object obj) throws IllegalAccessException;
		void set(Object obj, Object value) throws IllegalAccessException;
	}
	
	static class FieldAccessor implements PropertyAccessor {

		private final Field field;
		
		public FieldAccessor(Field field) {
			field.setAccessible(true);

			this.field = field;
		}

		public Field getField() {
			return field;
		}

		public String getName() {
			return field.getName();
		}

		public Class getType() {
			return field.getType();
		}

		public boolean isReadable() {
			return true;
		}

		public boolean isWritable() {
			return true;
		}

		public Object get(Object obj) throws IllegalAccessException {
			try {
				return field.get(obj);
			}
			catch (IllegalAccessException e) {
				throw e;
			}
			catch (Exception e) {
				throw new IllegalAccessException("Could not get field " + field + " value on object " + obj + ": " + e.toString());
			}
		}

		public void set(Object obj, Object value) throws IllegalAccessException {
			try {
				field.set(obj, value);
			}
			catch (IllegalAccessException e) {
				throw e;
			}
			catch (Exception e) {
				throw new IllegalAccessException("Could not set field " + field + " on object " + obj + " to value " + value + ": " + e.toString());
			}
		}
	}
	
	static class MethodAccessor implements PropertyAccessor {

		private final Method getter;
		private final Method setter;
		
		private final String name;

		public MethodAccessor(Method getter, Method setter, String name) {
			
			if (getter != null)
				getter.setAccessible(true);
			if (setter != null)
				setter.setAccessible(true);
			
			this.getter = getter;
			this.setter = setter;
			
			this.name = name;
		}

		public Method getGetter() {
			return getter;
		}

		public Method getSetter() {
			return setter;
		}

		public String getName() {
			return name;
		}

		public Class getType() {
			return (getter != null ? getter.getReturnType() : setter.getParameterTypes()[0]);
		}

		public boolean isReadable() {
			return getter != null;
		}

		public boolean isWritable() {
			return setter != null;
		}

		public Object get(Object obj) throws IllegalAccessException {
			try {
				return getter.invoke(obj);
			}
			catch (IllegalAccessException e) {
				throw e;
			}
			catch (Exception e) {
				throw new IllegalAccessException("Could not invoke getter " + getter + " on object " + obj + ": " + e.toString());
			}
		}

		public void set(Object obj, Object value) throws IllegalAccessException {
			try {
				setter.invoke(obj, value);
			}
			catch (IllegalAccessException e) {
				throw e;
			}
			catch (Exception e) {
				throw new IllegalAccessException("Could not invoke setter " + setter + " on object " + obj + " with " + value + ": " + e.toString());
			}
		}
	}
	
	public static class Property {
		
		private final Object obj;
		private final PropertyAccessor accessor;

		public Property(Object obj, PropertyAccessor accessor) {
			this.obj = obj;
			this.accessor = accessor;
		}
		
		public String getName() {
			return accessor.getName();
		}
		
		public Class getType() {
			return accessor.getType();
		}

		public boolean isReadable() {
			return accessor.isReadable();
		}

		public boolean isWritable() {
			return accessor.isWritable();
		}

		public Object getValue() throws IllegalAccessException {
			return accessor.get(obj);
		}

		public void setValue(Object value) throws IllegalAccessException {
			accessor.set(obj, value);
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy