Mirror.java

package eu.javaexperience.reflect;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import eu.javaexperience.arrays.ArrayTools;
import eu.javaexperience.collection.CollectionTools;
import eu.javaexperience.collection.map.MapTools;
import eu.javaexperience.interfaces.simple.getBy.GetBy1;
import eu.javaexperience.interfaces.simple.getBy.GetBy2;
import eu.javaexperience.io.IOTools;
import eu.javaexperience.log.JavaExperienceLoggingFacility;
import eu.javaexperience.log.LogLevel;
import eu.javaexperience.log.Loggable;
import eu.javaexperience.log.Logger;
import eu.javaexperience.log.LoggingTools;
import eu.javaexperience.semantic.references.MayNotNull;
import eu.javaexperience.text.Format;
import eu.javaexperience.text.StringTools;

public class Mirror
{
	protected static class MirrorLogger
	{
		//initialize as last, beacuse it can cause initialization loop error
		protected static final Logger LOG = JavaExperienceLoggingFacility.getLogger(new Loggable("Mirror"));
	}
	
	private Mirror(){}
	
	public static final Object undefined = new Object();
	
	public static <T> T tryCastOrNull(Object o, Class<T> cls)
	{
		if(o == null)
			return null;
		if(cls.isAssignableFrom(o.getClass()))
			return cls.cast(o);
		
		return null;
	}
	
	public static Object getObjectFieldOrNull(Object o, String field)
	{
		try
		{
			return getClassFieldOrNull(o.getClass(),field).get(o);
		}
		catch(Exception e)
		{
			return null;
		}
	}
	
	public static Object getObjectFieldByClassOrNull(Class cls,Object o, String field)
	{
		try
		{
			return getClassFieldOrNull(cls,field).get(o);
		}
		catch(Exception e)
		{
			return null;
		}
	}
	
	public static Object getObjectFieldByClassOrNull(String cls,Object o, String field)
	{
		try
		{
			return getClassFieldOrNull(cls,field).get(o);
		}
		catch(Exception e)
		{
			return null;
		}
	}
	
	/**
	 * Visszatért az osztály elérhetővé tett metódusával 
	 * 
	 * */
	public static Method getClassMethodOrNull(String cls,String method,Class<?>... types)
	{
		try
		{
			Method ret = Class.forName(cls).getDeclaredMethod(method, types);
			setAccessible(ret);
			return ret;
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * Visszatért az osztály elérhetővé tett mezőjével
	 * 
	 * */
	public static Field getClassFieldOrNull(String cls,String field)
	{
		try
		{
			Field ret = Class.forName(cls).getDeclaredField(field);
			setAccessible(ret);
			return ret;
		}
		catch(Exception e)
		{
			return null;
		}
	}
	
	/**
	 * Visszatért az osztály elérhetővé tett metódusával 
	 * 
	 * */
	public static Method getClassMethodOrNull(Class<?> cls,String method,Class<?>... types)
	{
		try
		{
			Method ret = cls.getDeclaredMethod(method, types);
			setAccessible(ret);
			return ret;
		}
		catch(Exception e)
		{
			return null;
		}
	}
	
	/**
	 * Visszatért az osztály elérhetővé tett metódusával 
	 * 
	 * */
	public static Method getClassUniqueNameMethodOrNull(Class<?> cls,String method)
	{
		try
		{
			Method[] mets = cls.getDeclaredMethods();
			for(Method m:mets)
				if(method.equals(m.getName()))
				{
					setAccessible(m);
					return m;
				}
		}
		catch(Exception e)
		{
		}
		return null;
	}
	
	/**
	 * Visszatért az osztály elérhetővé tett mezőjével
	 * 
	 * */
	public static Field getClassFieldOrNull(Class<?> cls,String field)
	{
		try
		{
			ClassData cd = getClassData(cls);
			for(Field f:cd.getAllFields())
			{
				if(field.equals(f.getName()))
				{
					setAccessible(f);
					return f;
				}
			}
		}
		catch(Exception e)
		{
			LoggingTools.tryLogFormat(MirrorLogger.LOG, LogLevel.DEBUG, "Exception while collecting ClassData about class `%s`", cls);
		}
		return null;
	}
	
	
	/**
	 * A PHP-ban az egyik legcsodásabb dolog amit ismerek a var_dump. Struktúráltan kiírja az Objektum mezőit és annak értéketi.
	 * Mikor még csak ismerkedtem a Java-val azt hittem alap hogy van ilyen a nyelvben, sajnos nem.
	 * De mostmár van! :D
	 * @param ps ahova írjuk az eredményt
	 * @param o objektum amit ki szeretnénk iratni
	 * @param c szint elválasztó
	 * @throws IOException 
	 * */
	public static void var_dump_throw(Appendable ps,Object o,String c) throws IllegalArgumentException, IllegalAccessException, IOException
	{
		var_dump(ps,o,1,c,new ArrayList<Object>());
	}
	
	public static void var_dump(Appendable ps,Object o,String c)
	{
		try
		{
			var_dump_throw(ps, o, c);
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	
	public static ClassData getClassData(Class<?> cls)
	{
		if(cls == null)
			return null;
		
		ClassData ret = LazyClassDataInit.data.get(cls);
	
		if(ret == null)
		{
			ret = new ClassData(cls);
			LazyClassDataInit.data.put(cls, ret);
		}
	
		return ret;
	}
	
	public static ClassData getClassData(String scls)
	{
		Class<?> cls = null;
		try
		{
			cls = Class.forName(scls);
		}
		catch (ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		
		if(cls == null)
			return null;
		
		ClassData ret = LazyClassDataInit.data.get(cls);
	
		if(ret == null)
		{
			ret = new ClassData(cls);
			LazyClassDataInit.data.put(cls, ret);
		}
	
		return ret;
	}
	
	protected static class LazyClassDataInit
	{
		protected static ConcurrentMap<Class<?>, ClassData> data = new ConcurrentHashMap<>();
	}
	public static char[] emptyCharArray = new char[0];
	
	
	
	public static enum Visibility
	{
		All,
		Default,
		Public,
		Protected,
		Private,
	}
	
	public static enum BelongTo
	{
		Any,
		Static,
		Instance,
	}
	
	public static enum Select
	{
		All,
		Is,
		IsNot
	}
	
	////PUBLIC | PROTECTED | PRIVATE | 		ABSTRACT | STATIC | FINAL | SYNCHRONIZED | NATIVE | STRICT;
	public static final class MethodSelector
	{
		final boolean all;			//1
		final Visibility vis;		//3
		final BelongTo belong;		//2
		final Select _abstract;		//2
		final Select _final;		//2
		final Select _synchronized;	//2
		final Select _native;		//2
		final Select _strict;		//2
		
		public MethodSelector(boolean all_not_just_self,Visibility visibility,BelongTo belong,Select _abstract,Select _final,Select _synchronized,Select _native, Select _strict)
		{
			all = all_not_just_self;
			vis = visibility;
			this.belong = belong;
			this._abstract = _abstract;
			this._final = _final;
			this._synchronized = _synchronized;
			this._native = _native;
			this._strict = _strict;
		}
		
		@Override
		public int hashCode()
		{
			int ret = 0;
			ret &= all?1:0;							// 0
			ret &= vis.ordinal() >> 1;				// 1-4
			ret &= belong.ordinal() >> 4;			// 5-6
			ret &= _abstract.ordinal() >> 6;		// 7-8
			ret &= _final.ordinal() >> 8;			// 9-10
			ret &= _synchronized.ordinal() >> 10;	// 11-12
			ret &= _native.ordinal() >> 12;			// 13-14
			ret &= _strict.ordinal() >> 14;			// 15-16
			
			return ret;
		}
		
		@Override
		public boolean equals(Object o)
		{
			if(this == o)
				return true;
			
			if(!(o instanceof MethodSelector))
				return false;
			
			MethodSelector dat = (MethodSelector) o;
			
			return
						all == dat.all
					&&
						vis == dat.vis
					&&
						belong == dat.belong
					&&
						_abstract == dat._abstract
					&&
						_final == dat._final
					&&
						_synchronized == dat._synchronized
					&&
						_native == dat._native
					&&
						_strict == dat._strict
			;
		}
	}
	
	//PUBLIC | PROTECTED | PRIVATE | STATIC | FINAL | TRANSIENT | VOLATILE;
	public static final class FieldSelector
	{
		final boolean all;
		final Visibility vis;
		final BelongTo belong;
		final Select _final;
		final Select _transient;
		final Select _volatile;
		
		public FieldSelector(boolean all_not_just_self,Visibility visibility,BelongTo belong,Select _final,Select _transient,Select _volatile)
		{
			all = all_not_just_self;
			vis = visibility;
			this.belong = belong;
			this._final = _final;
			this._transient = _transient;
			this._volatile = _volatile;
		}
		
		@Override
		public int hashCode()
		{
			int ret = 0;
			ret &= all?1:0;							// 0
			ret &= vis.ordinal() >> 1;				// 1-4
			ret &= belong.ordinal() >> 4;			// 5-6
			ret &= _transient.ordinal() >> 6;		// 7-8
			ret &= _final.ordinal() >> 8;			// 9-10
			ret &= _volatile.ordinal() >> 10;	// 11-12
			return ret;
		}
		
		@Override
		public boolean equals(Object o)
		{
			if(this == o)
				return true;
			
			if(!(o instanceof FieldSelector))
				return false;
			
			FieldSelector dat = (FieldSelector) o;
			
			return
						all == dat.all
					&&
						vis == dat.vis
					&&
						belong == dat.belong
					&&
						_transient == dat._transient
					&&
						_final == dat._final
					&&
						_volatile == dat._volatile
			;
		}
	}
	
	/**
	 * Osztályok adatinak összegyüjtése
	 * */
	public static class ClassData
	{
		final Class<?> cls;
		final Method[] self_methods;
		final Method[] all_methods;
		
		final Field[] self_fields;
		final Field[] all_fields;
		
		final Class<?>[] direct_interfaces;
		final Class<?>[] all_interfaces;
	
		final Class<?> direct_superclass;
		final Class<?>[] all_superclass;
		
		private ClassData(@MayNotNull Class<?> cls)
		{
			this.cls = cls;
			
			self_methods = cls.getDeclaredMethods();
			self_fields = cls.getDeclaredFields();
			
			direct_interfaces = cls.getInterfaces();
			direct_superclass = cls.getSuperclass();
			
			Class<?>[] interf = direct_interfaces;
			
			if(direct_superclass != null)
			{
				ClassData dat = getClassData(direct_superclass);
				all_methods = ArrayTools.arrayConcat(self_methods, dat.all_methods);
				all_fields = ArrayTools.arrayConcat(self_fields, dat.all_fields);
				for(Class<?> in:direct_interfaces)
					interf = ArrayTools.arrayConcat(interf, getClassData(in).all_interfaces);

				all_superclass = ArrayTools.arrayAppend(direct_superclass, dat.all_superclass);
			}
			else//ez csak az Object-nél lesz igaz
			{
				all_methods = self_methods;
				all_fields = self_fields;
				all_superclass = emptyClassArray;
			}
			
			for(Field f:self_fields)
				trySetAccessible(f);
			
			for(Method m:self_methods)
				trySetAccessible(m);
			
			all_interfaces = interf;
		}
		
		public Class<?> getSubjectClass()
		{
			return cls;
		}
		
		public Method[] getSelfMethods()
		{
			return ArrayTools.copy(self_methods);
		}
		
		public Method[] getAllMethods()
		{
			return ArrayTools.copy(all_methods);
		}
		
		public Field[] getSelfFields()
		{
			return ArrayTools.copy(self_fields);
		}
		
		public Field[] getAllFields()
		{
			return ArrayTools.copy(all_fields);
		}
		
		ConcurrentMap<MethodSelector, Method[]> metSel = new ConcurrentHashMap<>();
		ConcurrentMap<FieldSelector, Field[]> fieldSel = new ConcurrentHashMap<>();
		
		//PUBLIC | PROTECTED | PRIVATE | 		ABSTRACT | STATIC | FINAL | SYNCHRONIZED | NATIVE | STRICT;
		public Method[] selectMethods(MethodSelector sel)
		{
			Method[] ret = metSel.get(sel);
			
			if(ret == null)
			{
				ret = select(sel);
				metSel.put(sel, ret);
			}
			
			return ret;
		}
	
		//PUBLIC | PROTECTED | PRIVATE | STATIC | FINAL | TRANSIENT | VOLATILE;
		public Field[] selectFields(FieldSelector sel)
		{
			Field[] ret = fieldSel.get(sel);
			
			if(ret == null)
			{
				ret = select(sel);
				fieldSel.put(sel, ret);
			}
			
			return ret;
		}
	
		
		public Method[] select(MethodSelector sel)
		{
			ArrayList<Method> met = new ArrayList<>();
			
			Method[] ms = sel.all?all_methods:self_methods;

			for(Method m:ms)
			{
				int mod = m.getModifiers();
				switch (sel.vis)
				{
				case Default:
					if(Modifier.isPublic(mod) || Modifier.isProtected(mod) || Modifier.isPrivate(mod))
						continue;

					break;
				case Private:
					if(!Modifier.isPrivate(mod))
						continue;
					
					break;
				case Protected:
					if(!Modifier.isProtected(mod))
						continue;
					
					break;
				case Public:
					if(!Modifier.isPublic(mod))
						continue;
					
					break;
				default:
				case All:
					break;
				}
				

				switch (sel.belong)
				{
				case Instance:
					if(Modifier.isStatic(mod))
						continue;
					
					break;
				case Static:
					if(!Modifier.isStatic(mod))
						continue;
					
					break;
				default:
				case Any:
					break;
				}
					
				if(acceptByMatch(Modifier.ABSTRACT, sel._abstract, mod))
					continue;
				
				if(acceptByMatch(Modifier.FINAL, sel._final, mod))
					continue;

				if(acceptByMatch(Modifier.SYNCHRONIZED, sel._synchronized, mod))
					continue;

				if(acceptByMatch(Modifier.NATIVE, sel._native, mod))
					continue;
				
				if(acceptByMatch(Modifier.STRICT, sel._strict, mod))
					continue;

				met.add(m);
			}
			
			return met.toArray(emptyMethodArray);
		}
		
		public static boolean acceptByMatch(int modifier,Select sel,int value)
		{
			switch (sel)
			{
			case Is:
				return (modifier & value) != modifier;
				
			case IsNot:
				return (modifier & value) != 0;

			default:
			case All:
				return false;
			}
		}
		
		public Field[] select(FieldSelector sel)
		{
			ArrayList<Field> met = new ArrayList<>();
			
			Field[] ms = sel.all?all_fields:self_fields;

			for(Field f:ms)
			{
				int mod = f.getModifiers();
				switch (sel.vis)
				{
				case Default:
					if(Modifier.isPublic(mod) || Modifier.isProtected(mod) || Modifier.isPrivate(mod))
						continue;

					break;
				case Private:
					if(!Modifier.isPrivate(mod))
						continue;
					
					break;
				case Protected:
					if(!Modifier.isProtected(mod))
						continue;
					
					break;
				case Public:
					if(!Modifier.isPublic(mod))
						continue;
					
					break;
				default:
				case All:
					break;
				}
				

				switch (sel.belong)
				{
				case Instance:
					if(Modifier.isStatic(mod))
						continue;
					
					break;
				case Static:
					if(!Modifier.isStatic(mod))
						continue;
					
					break;
				default:
				case Any:
					break;
				}
					
				if(acceptByMatch(Modifier.TRANSIENT, sel._transient, mod))
					continue;
				
				if(acceptByMatch(Modifier.FINAL, sel._final, mod))
					continue;

				if(acceptByMatch(Modifier.VOLATILE, sel._volatile, mod))
					continue;

				met.add(f);
			}
			
			return met.toArray(emptyFieldArray);
		}

		public Field getFieldByName(String k)
		{
			for(Field f:getAllFields())
			{
				if(f.getName().equals(k))
				{
					return f;
				}
			}
			return null;
		}
		
		public void addClassesAndIntefaces(Collection<Class> dst)
		{
			dst.add(cls);
			CollectionTools.inlineAdd(dst, all_superclass);
			CollectionTools.inlineAdd(dst, all_interfaces);
		}

		public Class[] getAllSuperClassAndInterfaces()
		{
			List<Class> ret = new ArrayList<>();
			addClassesAndIntefaces(ret);
			return ret.toArray(emptyClassArray);
		}
	}
	
	public static void fillObjectPublicFieldIntoMap(Object o,Map<String,Object> map) throws IllegalArgumentException, IllegalAccessException
	{
		ClassData dat = getClassData(o.getClass());
		Field[] fs = dat.select(new FieldSelector(true, Visibility.Public, BelongTo.Instance, Select.All, Select.All, Select.All));
		for(Field f:fs)
			map.put(f.getName(), f.get(o));
	}
	
	public static void fillMapIntoObject(Map<String,Object> map, Object o) throws IllegalArgumentException, IllegalAccessException
	{
		ClassData dat = getClassData(o.getClass());
		Field[] fs = dat.select(new FieldSelector(true, Visibility.All, BelongTo.Instance, Select.All, Select.All, Select.All));
		for(Field f:fs)
			if(map.containsKey(f.getName()))
				try
				{
					f.set(o, map.get(f.getName()));
				}
				catch (Exception e)
				{
					if(MirrorLogger.LOG.mayLog(LogLevel.DEBUG))
					{
						LoggingTools.tryLogFormat(MirrorLogger.LOG, LogLevel.DEBUG, "Exception while invoking fillMapIntoObject(`%s`, `%s`)", map, o);
					}
				}
	}
	
	public static void tryFillMapIntoObjectCast(Map<String,Object> map, Object o)
	{
		ClassData dat = getClassData(o.getClass());
		Field[] fs = dat.select(FieldSelectTools.SELECT_ALL_INSTANCE_FIELD);
		for(Field f:fs)
		{
			if(map.containsKey(f.getName()))
			{
				CastTo cast = CastTo.getCasterRestrictlyForTargetClass(f.getType());
				if(null != cast)
				{
					Object set = map.get(f.getName());
					set = cast.cast(set);
					try
					{
						f.set(o, set);
					}
					catch (Exception e)
					{
						if(MirrorLogger.LOG.mayLog(LogLevel.DEBUG))
						{
							LoggingTools.tryLogFormat(MirrorLogger.LOG, LogLevel.DEBUG, "Exception while invoking tryFillMapIntoObjectCast(`%s`, `%s`)", map, o);
						}
					}
				}
			}
		}
	}
	
	public static Number castNumberTo(Number n,Class<? extends Number> cls)
	{
		cls = (Class<Number>) PrimitiveTools.translatePrimitiveToObjectType(cls);
		if(cls == Integer.class)
			return n.intValue();
		else if(cls == Long.class)
			return n.longValue();
		else if(cls == Double.class)
			return n.doubleValue();
		else if(cls == Float.class)
			return n.floatValue();
		else if(cls == Byte.class)
			return n.byteValue();
		else if(cls == Short.class)
			return n.shortValue();
		return n;
	}
	
	private static void var_dump(Appendable ps,Object obj,int lvl,String c,ArrayList<Object> disa) throws IllegalArgumentException, IllegalAccessException, IOException
	{
		if(obj == null)
		{
			ps.append("null,\n");
			return;
		}
		
		if(disa.contains(obj))
		{
			ps.append("@[");
			ps.append(Integer.toHexString(obj.hashCode()));
			ps.append("]\n");
			return;
		}
		
		disa.add(obj);
		
		String ls = StringTools.repeatString(c, lvl-1);
		Class<?> cls = obj.getClass();
		if(cls.isArray())
		{
			ps.append("<");
			printClassType(ps, cls);
			ps.append("> ");
			ps.append(Integer.toHexString(System.identityHashCode(obj)));
			ps.append("\n");
			ps.append(ls);
			ps.append("{\n");
			
			for(int i=0;i<Array.getLength(obj);i++)
			{
				ps.append(ls);
				ps.append(c);
				ps.append("[");
				ps.append(String.valueOf(i));
				ps.append("] : ");
				var_dump(ps, Array.get(obj, i), lvl+1,c,disa);
			}
			
			ps.append(ls);
			ps.append("},\n");
		}
		else if(PrimitiveTools.isPrimitiveTypeObject(cls)) //primitiv vagy String
		{
			ps.append("<");
			ps.append(cls.getName());
			ps.append(">");
			ps.append(" : ");
			ps.append(obj.toString());
			ps.append(",\n");
		}
		else if(cls.equals(String.class))
		{
			ps.append("<");
			ps.append(cls.getName());
			ps.append(">");
			ps.append(" : ");
			ps.append("\"");
			ps.append(obj.toString());
			ps.append("\"");
			ps.append(",\n");
		}
		else //sima objektum
		{
			//collectfileds ne csak ezen osztályét
			Field[] fields = collectClassFields(cls, false);
			
			ps.append("<");
			printClassType(ps,cls);
			ps.append("> ");
			ps.append(Integer.toHexString(System.identityHashCode(obj)));
			ps.append(":\n");
			ps.append(ls);
			ps.append("{\n");
			
			for(Field f:fields)
			{
				if(Modifier.isStatic(f.getModifiers()))
					continue;
				
				ps.append(ls);
				ps.append(c);
				ps.append(f.getName());
				ps.append("(");
				printClassType(ps,f.getType());
				ps.append(")");
				ps.append(" : ");
				
				if(trySetAccessible(f))
				{
					Object val = f.get(obj);
					var_dump(ps, val, lvl+1,c,disa);
				}
			}
			
			ps.append(ls);
			ps.append("},\n");
		}
	}
	
	public static final Class<?>[] emptyClassArray = new Class[0];
	
	public static Field[] collectClassFields(Class<?> cls,boolean publicOnly)
	{
		ArrayList<Field> l = new ArrayList<>();
		collectClassFields(cls,l,publicOnly);
		return l.toArray(emptyFieldArray); 
	}
	
	public static void collectClassFields(Class<?> cls,ArrayList<Field> ret,boolean publicOnly)
	{
		if(cls.isInterface() || cls == Object.class)
		{
			/**
			 * TeaVm: function jl_Class_getDeclaredFields($this)
			 * dies here:
			 * $this.$declaredFields = $rt_createArray(jlr_Field, $jsFields.length);
			 * when we try to discover an interface.
			 */
			return;
		}
		
		for(Field f:publicOnly?cls.getFields():cls.getDeclaredFields())
		{
			ret.add(f);
		}
		
		Class<?> sup = cls.getSuperclass();
		if(sup != null)
		{
			collectClassFields(sup, ret, publicOnly);
		}
		
		/*for(Class<?> c:cls.getInterfaces())
		{
			if(c != null)
			{
				collectClassFields(c, ret, publicOnly);
			}
		}*/
	}
	
	public static Method[] collectClassMethods(Class<?> cls,boolean publicOnly)
	{
		ArrayList<Method> l = new ArrayList<>();
		collectClassMethods(cls,l,publicOnly);
		return l.toArray(emptyMethodArray); 
	}
	
	public static void collectClassMethods(Class<?> cls,ArrayList<Method> ret,boolean publicOnly)
	{
		for(Method f:publicOnly?cls.getMethods():cls.getDeclaredMethods())
			ret.add(f);
		
		Class<?> sup = cls.getSuperclass();
		if(sup != null)
			collectClassMethods(sup, ret, publicOnly);
		
		for(Class<?> c:cls.getInterfaces())
			if(c != null)
				collectClassMethods(c, ret, publicOnly);
	}
	
	public static Class<?> getFinalComponentClass(Class<?> cls)
	{
		if(cls.isArray())
			return getFinalComponentClass(cls.getComponentType());
		return cls;
	}
	
	public static int determineArrayDeep(Class<?> c)
	{
		return determineArrayDeep(c, 0);
	}
	
	private static int determineArrayDeep(Class<?> c,int lvl)
	{
		if(!c.isArray())
			return lvl;
		
		return determineArrayDeep(c.getComponentType(),lvl+1);
	}
	
	
	public static void printClassType(Appendable ps,Class<?> cls) throws IOException
	{
		int deep = determineArrayDeep(cls);
		Class<?> comp = getFinalComponentClass(cls);
		ps.append(comp.getName());
		for(int i=0;i<deep;i++)
			ps.append("[]");
	}
	
	public static void appendClassType(StringBuilder ps,Class<?> cls)
	{
		int deep = determineArrayDeep(cls);
		Class<?> comp = getFinalComponentClass(cls);
		ps.append(comp.getCanonicalName());
		for(int i=0;i<deep;i++)
			ps.append("[]");
	}
	
	public static String getClassTypeString(Class<?> cls)
	{
		StringBuilder sb = new StringBuilder();
		appendClassType(sb, cls);
		return sb.toString();
	}
	
	public static Field setAccessible(Field f)
	{
		f.setAccessible(true);
		return f;
	}
	
	public static Method setAccessible(Method f)
	{
		f.setAccessible(true);
		return f;
	}
	
	public static boolean trySetAccessible(AccessibleObject a)
	{
		try
		{
			a.setAccessible(true);
		}
		catch(Throwable t)
		{
			return false;
		}
		return true;
	}
	
	public static Object newInstanceOrNull(Class<?> cls)
	{
		try
		{
			return cls.newInstance();
		}
		catch (Exception e)
		{}
		
		return null;
	}

	public static Object invokeClassMethod(Class<?> cls, String method, Object This)
	{
		Method m = getClassMethodOrNull(cls, method);
		if(m == null)
			return null;
		try
		{
			return m.invoke(This);
		}
		catch(Exception e)
		{}
		return null;
	}
	
	public static Object invokeClassMethod(Object This, String method)
	{
		Method m = getClassMethodOrNull(This.getClass(), method);
		if(m == null)
			return null;
		try
		{
			return m.invoke(This);
		}
		catch(Exception e)
		{}
		return null;
	}
	
	public static Object invokeClassMethod(String scls, Object This, String method)
	{
		try
		{
			Class<?> cls = getClassData(scls).cls;
			
			Method m = getClassMethodOrNull(cls, method);
			
			if(m == null)
				return null;

			return m.invoke(This);
		}
		catch(Exception e)
		{e.printStackTrace();}
		return null;
	}
	
	
	/**
	 * Perfect method to determine full qualified class name from byte code.
	 * 
	 * http://stackoverflow.com/questions/1649674/resolve-class-name-from-bytecode
	 * */
	public static String getClassName(InputStream is) throws Exception
	{
		DataInputStream dis = new DataInputStream(is);
		dis.readLong(); // skip header and class version
		int cpcnt = (dis.readShort()&0xffff)-1;
		int[] classes = new int[cpcnt];
		String[] strings = new String[cpcnt];
		for(int i=0; i<cpcnt; i++) {
			int t = dis.read();
			if(t==7) classes[i] = dis.readShort()&0xffff;
			else if(t==1) strings[i] = dis.readUTF();
			else if(t==5 || t==6) { dis.readLong(); i++; }
			else if(t==8) dis.readShort();
			else dis.readInt();
		}
		dis.readShort(); // skip access flags
	    return strings[classes[(dis.readShort()&0xffff)-1]-1].replace('/', '.');
	}
	
	/**
	 * if @param o is an array first element will returned
	 * otherwise the original value
	 * */
	public static Object tryUnpack(Object o)
	{
		if(null == o)//note obj instanceof Object[] is false e.g. for int[]
			return null;
		
		if(o.getClass().isArray())
			if(Array.getLength(o) > 0)
				return Array.get(o, 0);
		
		return o;
	}
	
	public static boolean in(int val, int... vals)
	{
		for(int i=0;i<vals.length;++i)
		{
			if(val == vals[i])
			{
				return true;
			}
		}
		return false;
	}
	
	public static boolean in(@MayNotNull Object val, @MayNotNull Object... vals)
	{
		for(int i=0;i<vals.length;++i)
		{
			if(val.equals(vals[i]))
			{
				return true;
			}
		}
		return false;
	}
	
	@Deprecated
	public static void throwSoftOrHardButAnyway(Throwable t)
	{
		propagateAnyway(t);
	}
	
	public static void propagateAnyway(Throwable t)
	{
		if(t instanceof InvocationTargetException)
		{
			Throwable tmp = ((InvocationTargetException)t).getCause();
			if(null != tmp)
			{
				t = tmp;
			}
		}
		
		if(t instanceof RuntimeException)
		{
			throw (RuntimeException) t;
		}
		else if(t instanceof Error)
		{
			throw (Error) t;
		}
		
		throw new RuntimeException(t);
	}
	
	public static <T> T valueOrDefault(T value, T _default)
	{
		if(null != value)
		{
			return value;
		}
		
		return _default;
	}
	
	public static boolean equals(Object o1, Object o2)
	{
		if(o1 == o2)
		{
			return true;
		}
		
		if(null != o1)
		{
			return o1.equals(o2);
		}
		
		return false;
	}
	
	public static int compare(int a, int b)
	{
		if(a > b)
		{
			return 1;
		}
		else if(b > a)
		{
			return -1;
		}
		else
		{
			return 0;
		}
	}
	
	public static <T> T shallowClone(T origin, Field[] toClone) throws IllegalArgumentException, IllegalAccessException, InstantiationException
	{
		T ret = (T) origin.getClass().newInstance();
		shallowClone(origin, ret, toClone);
		return ret;
	}
	
	public static <T> void shallowClone(T origin, T newObject, Field[] toClone) throws IllegalArgumentException, IllegalAccessException
	{
		for(Field f:toClone)
		{
			Object src = f.get(origin);
			f.set(newObject, src);
		}
	}
	
	public static final int[] emptyIntArray = new int[0];
	public static final int[][] emptyIntIntArray = new int[0][0];
	public static final byte[] emptyByteArray = new byte[0];
	public static final long[] emptyNLongArray = new long[0];
	public static final Integer[] emptyIntegerArray = new Integer[0];
	public static final Method[] emptyMethodArray = new Method[0];
	
	public static final Field[] emptyFieldArray = new Field[0];
	
	public static final String[] emptyStringArray = new String[0];
	
	public static final String[][] emptyStringArrayArray = new String[0][0];
	
	public static final Object[] emptyObjectArray = new Object[0];
	
	public static final Boolean[] emptyBooleanArray = new Boolean[0];
	
	public static final Long[] emptyLongArray = new Long[0];
	
	protected static InputStream getResource(String relative)
	{
		HashSet<ClassLoader> cls = new HashSet<>();
		ClassLoader cl = Thread.currentThread().getContextClassLoader();
		do
		{
			InputStream is = cl.getResourceAsStream(relative);
			if(null != is)
			{
				return is;
			}
			
			//Cyclic reference in ClassLoaders
			if(cls.contains(cl))
			{
				return null;
			}
			
			cls.add(cl);
			
			cl = cl.getParent();
			
			if(null == cl)
			{
				return null;
			}
		}
		while(true);
	}
	
	public static byte[] getJavaResource(String relativePath) throws IOException
	{
		try(InputStream is = getResource(relativePath))
		{
			if(null == is)
			{
				return Mirror.emptyByteArray;
			}
			return IOTools.loadAllFromInputStream(is);
		}
	}
	
	public static byte[] getPackageResource(Class<?> cls, String string) throws IOException
	{
		String pack = StringTools.getSubstringBeforeLastString(StringTools.replaceAllStrings(cls.getCanonicalName(), ".", "/"), "/");
		
		return getJavaResource(pack+"/"+string);
	}
	
	public static enum CloningMethod implements GetBy2<Object, Field, Object>
	{
		SKIP
		{
			@Override
			public Object getBy(Field a, Object b)
			{
				return null;
			}
		},
		REFERENCE_ASSIGN
		{
			@Override
			public Object getBy(Field a, Object b)
			{
				return b;
			}
		},
		DEEP_COPY
		{
			@Override
			public Object getBy(Field a, Object b)
			{
				try
				{
					return Mirror.clone(b, (GetBy1) SIMPLE_CREATOR, DEEP_COPY);
				}
				catch (Exception e)
				{
					throw new RuntimeException(e);
				}
			}
		},
		//CUSTOM;
	}
	
	public static final GetBy1<Object, Class<?>> SIMPLE_CREATOR = new GetBy1<Object, Class<?>>()
	{
		@Override
		public Object getBy(Class<?> a)
		{
			try
			{
				return a.newInstance();
			}
			catch(Exception e)
			{
				throw new RuntimeException(e);
			}
		}
	};
	
	public static boolean isStatic(Member f)
	{
		int mod = f.getModifiers();
		return Modifier.isStatic(mod);
	}
	
	public static boolean isPublic(Member f)
	{
		int mod = f.getModifiers();
		return Modifier.isPublic(mod);
	}
	
	public static Object clone(Object obj, GetBy1<Object, Class<?>> simpleCreator, GetBy2<Object, Field, Object> cloner) throws IllegalArgumentException, IllegalAccessException
	{
		Object ret = simpleCreator.getBy(obj.getClass());
		ClassData cd = getClassData(obj.getClass());
		for(Field f:cd.all_fields)
		{
			if(!isStatic(f))
			{
				f.set(ret, cloner.getBy(f, f.get(obj)));
			}
		}
		
		return ret;
	}
	
	public static Object simpleShallowClone(Object obj) throws IllegalArgumentException, IllegalAccessException
	{
		return clone(obj, SIMPLE_CREATOR, CloningMethod.REFERENCE_ASSIGN);
	}

	public static void extractFieldsToMap(Object in, Map<String, Object> values, FieldSelector select) throws IllegalArgumentException, IllegalAccessException
	{
		ClassData dat = getClassData(in.getClass());
		Field[] fs = dat.select(select);
		for(Field f:fs)
		{
			values.put(f.getName(), f.get(in));
		}
	}

	public static Object tryGetFieldValue(Object o, String key)
	{
		if(null == o)
		{
			return null;
		}
		
		try
		{
			Class c = o.getClass();
			Field f = c.getDeclaredField(key);
			f.setAccessible(true);
			return f.get(o);
		}
		catch(Exception e)
		{
			
		}
		return null;
	}
	
	public static Class<?> extracClass(Type t)
	{
		if(t instanceof Class)
		{
			return (Class<?>) t;
		}
		else if(t instanceof GenericArrayType)
		{
			return Array.newInstance(extracClass(((GenericArrayType)t).getGenericComponentType()),0).getClass();
		}
		else if(t instanceof ParameterizedType)
		{
			return extracClass(((ParameterizedType)t).getRawType());
		}
		
		return Object.class;
	}
	
	public static boolean isVoid(Class cls)
	{
		if(null != cls)
		{
			return void.class == cls || Void.class == cls; 
		}
		
		return false;
	}

	protected static final int SHALLOW_EXCLUDE_MODIFIER = 0
		| Modifier.STATIC 
		| Modifier.TRANSIENT
		//| Modifier.
		;
	
	public static boolean shallowEquals(Object me, Object obj)
	{
		if(null == me || null == obj)
		{
			return false;
		}
		
		if(me == obj)
		{
			return true;
		}
		
		if(!me.getClass().isAssignableFrom(obj.getClass()))
		{
			return false;
		}
		
		Field[] fs = getClassData(me.getClass()).self_fields;
		try
		{
			for(Field f:fs)
			{
				if(0 == (f.getModifiers() & SHALLOW_EXCLUDE_MODIFIER))
				{
					if(!Mirror.equals(f.get(me), f.get(obj)))
					{
						return false;
					}
				}
			}
		}
		catch(Exception e)
		{
			propagateAnyway(e);
		}
		
		return true;
	}
	
	public static int shallowHashCode(Object me)
	{
		if(null == me)
		{
			return 0;
		}
		
		int ret = 27;
		
		Field[] fs = getClassData(me.getClass()).self_fields;
		try
		{
			for(Field f:fs)
			{
				if(0 == (f.getModifiers() & SHALLOW_EXCLUDE_MODIFIER))
				{
					int ph = 0;
					Object o = f.get(me);
					if(null != o)
					{
						ph = o.hashCode();
					}
					
					ret = 31* ret + ph;
				}
			}
		}
		catch(Exception e)
		{
			propagateAnyway(e);
		}
		
		return ret;
	}

	public static URL getClassUrl(Class<?> cls)
	{
		String file = StringTools.replaceAllStrings(cls.getName(), ".", "/")+".class";
		return Thread.currentThread().getContextClassLoader().getResource(file);
	}
	
	public static byte[] getClassBytecode(Class<?> cls) throws IOException
	{
		String file = StringTools.replaceAllStrings(cls.getName(), ".", "/")+".class";
		InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(file);
		if(null != is)
		{
			try
			{
				return IOTools.loadAllFromInputStream(is);
			}
			finally
			{
				is.close();
			}
		}
		
		return null;
	}

	public static void setProperty(Object subject, String field, Object value) throws IllegalArgumentException, IllegalAccessException
	{
		ClassData cd = getClassData(subject.getClass());
		for(Field f:cd.getAllFields())
		{
			if(f.getName().equals(field))
			{
				f.set(subject, value);
			}
		}
	}

	public static <T> T passOnlyInstanceOf
	(
		Class<T> cls,
		Object object
	)
	{
		if(null != object)
		{
			if(cls.isAssignableFrom(object.getClass()))
			{
				return (T) object;
			}
		}
		
		return null;
	}
	
	public static boolean isRestAppicableForType
	(
		Class forClass,
		int startIndexInclusive,
		boolean mayNull,
		Class... inputClasses
	)
	{
		for(int i=startIndexInclusive;i<inputClasses.length;++i)
		{
			Class c = inputClasses[i];
			if(null == c)
			{
				if(!mayNull)
				{
					return false;
				}
				else
				{
					continue;
				}
			}
			
			if(!forClass.isAssignableFrom(c))
			{
				return false;
			}
		}
		
		return true;
	}
	
	public static <T> Object[] varargize(Class<T> reqClass, int startIndexInclusiveDst, Object... params)
	{
		T[] varargs = (T[]) Array.newInstance(reqClass, params.length-startIndexInclusiveDst);
		Object[] ret = new Object[startIndexInclusiveDst+1]; 
		for(int i=0;i<=startIndexInclusiveDst;++i)
		{
			ret[i] = params[i];
		}
		
		ret[startIndexInclusiveDst] = varargs;
		
		for(int i=0;i<varargs.length;++i)
		{
			varargs[i] = (T) params[startIndexInclusiveDst+i];
		}
		
		return ret;
	}
	
	public static <T> Class[] getClasses(T[] arr)
	{
		Class[] ret = new Class[arr.length];
		for(int i = 0;i<arr.length;++i)
		{
			Object o = arr[i];
			ret[i] = null == o?null:o.getClass();
		}
		return ret;
	}
	
	public static Object callMethod
	(
		Object subject,
		String method,
		Object... params
	)
		throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
	{
		Class[] obj = new Class[params.length];
		ClassData cd = getClassData(subject.getClass());
		Method func = null;
		
		Class[] pclasses = getClasses(params);
		for(Method m:cd.all_methods)
		{
			if(isStatic(m))
			{
				continue;
			}
			
			if(method.equals(m.getName()))
			{
				Class[] ps = m.getParameterTypes();
				if(ps.length < params.length)
				{
					throw new RuntimeException("Parameter count mismatch: req_params:"+Arrays.toString(ps)+", given: "+pclasses);
				}
				//varargs 
				else if(ps.length > params.length)
				{
					if(!isRestAppicableForType(ps[ps.length-1], ps.length-1, true, pclasses))
					{
						throw new RuntimeException("Can't varargize parameters: req_params:"+Arrays.toString(ps)+", given: "+pclasses);
					}
					
					params = varargize(ps[ps.length-1], ps.length-1, params);
				}
				
				for(int i=0;i<ps.length;++i)
				{
					Class fc = ps[i];
					Class pc = pclasses[i];
					if(null != pc && !fc.isAssignableFrom(pc))
					{
						throw new RuntimeException("Invalid argument type: req: "+fc+" given: "+pc);
					}
				}
				
				func = m;
				break;
			}
		}
		
		if(null == func)
		{
			throw new RuntimeException("Method (\""+method+"\") not found in :"+subject.getClass());
		}
		
		return func.invoke(subject, params);
	}

	public static <T> boolean trySetObjectField
	(
		Class<T> cls,
		T instance,
		String field,
		Object value
	)
	{
		try
		{
			Field ret = cls.getDeclaredField(field);
			setAccessible(ret);
			ret.set(instance, value);
			return true;
		}
		catch(Exception e)
		{
			return false;
		}
	}

	public static String usualToString(Object obj)
	{
		ClassData cd = Mirror.getClassData(obj.getClass());
		Field[] fs = cd.selectFields(FieldSelectTools.SELECT_ALL_INSTANCE_FIELD);
		StringBuilder sb = new StringBuilder();
		sb.append(obj.getClass().getSimpleName());
		sb.append(": {");
		
		boolean hasOut = false;
		for(Field f:fs)
		{
			Class ft = f.getType();
			if(hasOut)
			{
				sb.append(", ");
			}
			sb.append(f.getName());
			sb.append(": ");
			try
			{
				Object o = f.get(obj);
				if(null == o)
				{
					sb.append("null");
				}
				else if(Collection.class.isAssignableFrom(o.getClass()))
				{
					sb.append(CollectionTools.toString((Collection<?>) o));
				}
				else if(Map.class.isAssignableFrom(o.getClass()))
				{
					sb.append(MapTools.toString((Map<?, ?>) o));
				}
				else if(Date.class.isAssignableFrom(o.getClass()))
				{
					sb.append(Format.UTC_SQL_TIMESTAMP_MS.format((Date)o));
				}
				else
				{
					sb.append(o);
				}
			}
			catch(Exception e)
			{
				e.printStackTrace();
			}
			
			hasOut = true;
		}
		
		sb.append("}");
		
		return sb.toString();
	}

	public static <T> Constructor<T> getDeclaredConstructor(Class<T> class1, Class... params)
	{
		try
		{
			Constructor<T> ret = class1.getDeclaredConstructor(params);
			ret.setAccessible(true);
			return ret;
		}
		catch(Exception e)
		{
			Mirror.propagateAnyway(e);
			return null;
		}
	}

	public static Method getDeclaredMethod(Class<Socket> class1, String name, Class... params)
	{
		try
		{
			Method ret = class1.getDeclaredMethod(name, params);
			ret.setAccessible(true);
			return ret;
		}
		catch(Exception e)
		{
			Mirror.propagateAnyway(e);
			return null;
		}
	}
	
	public static Object tryCallBeanMethod(Method m, Object subject)
	{
		try
		{
			return m.invoke(subject);
		}
		catch(Exception e)
		{
			Mirror.propagateAnyway(e);
			return null;
		}
	}
	
	public static Object tryCallMethod(Method m, Object subject, Object... params)
	{
		try
		{
			return m.invoke(subject, params);
		}
		catch(Exception e)
		{
			Mirror.propagateAnyway(e);
			return null;
		}
	}
	
	protected static class CheckedExceptionUncheckedThrowHelper<T extends Throwable>
	{
		public void doThrow(Throwable t) throws T
		{
			throw (T) t;
		}
	}
	
	protected static CheckedExceptionUncheckedThrowHelper THROW_HELPER = new CheckedExceptionUncheckedThrowHelper();
	
	//one of the java's nasties hacks, i love it!. it's so broken and so cute
	public static void throwCheckedExceptionAsUnchecked(Throwable t)
	{
		((CheckedExceptionUncheckedThrowHelper<RuntimeException>)THROW_HELPER).doThrow(t);
	}
}