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

io.github.toolfactory.jvm.util.ObjectProvider Maven / Gradle / Ivy

/*
 * This file is part of ToolFactory JVM driver.
 *
 * Hosted at: https://github.com/toolfactory/jvm-driver
 *
 * --
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019-2021 Luke Hutchison, Roberto Gentili
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without
 * limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
 * EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
 * OR OTHER DEALINGS IN THE SOFTWARE.
 */
package io.github.toolfactory.jvm.util;


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;

import io.github.toolfactory.jvm.Info;
import io.github.toolfactory.jvm.function.template.Supplier;


@SuppressWarnings({"unchecked", "null"})
public class ObjectProvider {
	private final List classNameItems;
	private Map> jVMVendorToClassSuffix;
	private final static String CLASS_NAME;
	private int jVMVersion;
	private String vendor;
	
	
	static {
		CLASS_NAME = ObjectProvider.class.getName();
	}
	
	public ObjectProvider(int... versions) {
		this.classNameItems = new CopyOnWriteArrayList();
		this.jVMVendorToClassSuffix = new LinkedHashMap<>();
		this.jVMVendorToClassSuffix.put("Oracle Corporation", new ArrayList());
		this.jVMVendorToClassSuffix.put("International Business Machines Corporation", Arrays.asList("ForSemeru"));
		jVMVersion = Info.Provider.getInfoInstance().getVersion();
		vendor = System.getProperty("java.vendor");
		TreeSet registeredVersions = new TreeSet<>();
		for (int i = 0; i < versions.length; i++) {
			if (jVMVersion >= versions[i]) {
				registeredVersions.add(versions[i]);
			}
		}
		for (Integer version : registeredVersions.descendingSet().toArray(new Integer[registeredVersions.size()])) {
			classNameItems.add("ForJava" + version);
		}
	}
	

	public  T getOrBuildObject(Class clazz, Map context) {
		List classNameOptionalItems = jVMVendorToClassSuffix.get(vendor);
		BuildingException mainException = null;
		if (classNameOptionalItems != null) {
			try {
				context.put("classNameOptionalItems", classNameOptionalItems);
				return getOrBuildObjectInternal(clazz, context);
			} catch (Throwable exc) {
				mainException = new BuildingException(
					Strings.compile(
						"Exception occurred while retrieving the implentation of class {} (jvm architecture: {}, jvm version: {}, jvm vendor: {})",
						clazz.getName(),
						Info.Provider.getInfoInstance().is64Bit() ? "x64" : "x86",
						jVMVersion, vendor
					),
					exc
				);
			}
		} else {
			for (Entry> jVMVendorToClassSuffix : this.jVMVendorToClassSuffix.entrySet()) {
				try {
					context.put("classNameOptionalItems", jVMVendorToClassSuffix.getValue());
					return getOrBuildObjectInternal(clazz, context);
				} catch (Throwable exc) {
					if (jVMVendorToClassSuffix.getKey().equals("Oracle Corporation")) {
						mainException = new BuildingException(
							Strings.compile(
								"Exception occurred while retrieving the implentation of class {} (jvm architecture: {}, jvm version: {}, jvm vendor: {})",
								clazz.getName(),
								Info.Provider.getInfoInstance().is64Bit() ? "x64" : "x86",
								jVMVersion, jVMVendorToClassSuffix.getKey()
							),
							exc
						);
					}
				}
			}
		}
		ExceptionHandler exceptionHandler = getExceptionHandler(context);
		if (exceptionHandler!= null) {
			return exceptionHandler.handle(this, clazz, context, mainException);
		}
		throw mainException;
	}


	boolean putClassNameOptionalItem(List classNameOptionalItems, String value) {
		if (!classNameOptionalItems.contains(value)) {
			synchronized (classNameOptionalItems) {
				if (!classNameOptionalItems.contains(value)) {
					classNameOptionalItems.clear();
					classNameOptionalItems.add(value);
					return true;
				}
			}
		}
		return false;
	}
	
	
	private  T getOrBuildObjectInternal(Class clazz, Map context) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {
		String className = clazz.getName();
		Collection searchedClasses = new LinkedHashSet<>();
		T object = getObject(clazz, context);	
		if (object != null) {
			return object;
		}
		context.put(CLASS_NAME, this);
		List classNameOptionalItems = (List)context.get("classNameOptionalItems");
		List classNameItems = Arrays.asList(new String[classNameOptionalItems.size() + 2]);
		classNameItems.set(0, className);
		for (int i = 0; i < classNameOptionalItems.size(); i++) {
			classNameItems.set(i + 2, classNameOptionalItems.get(i));
		}
		classNameItems = new ArrayList(classNameItems);
		while (classNameItems.size() > 1) {
			for (String classNameItem : this.classNameItems) {
				try {
					classNameItems.set(1, classNameItem);
					object = (T)retrieveClass(classNameItems, searchedClasses, "$").getDeclaredConstructor(Map.class).newInstance(context);
					context.put(className, object);
					return object;
				} catch (ClassNotFoundException exc) {
					continue;
				}
			}			
			classNameItems.remove(classNameItems.size() -1);
		}
		
		if (!Modifier.isAbstract(clazz.getModifiers()) && !clazz.isInterface()) {
			try {
				return (T) clazz.getDeclaredConstructor(Map.class).newInstance(context);
			} catch (Throwable exc) {
				throw new BuildingException("Unable to build the related object of " + clazz.getName(), exc);
			}
		}
		Class superClass = clazz.getSuperclass();
		Class[] interfaces;
		if (superClass != null && !superClass.equals(Object.class)) {
			try {
				return (T)getOrBuildObject((Class)superClass, context);
			} catch (BuildingException exc) {
				throw new BuildingException(
					"Unable to build the related object of " + clazz.getName() + ": " + Strings.join(", ", searchedClasses) + " have been searched without success",
					exc
				);
			}
		} else if((interfaces = clazz.getInterfaces()).length > 0) {
			for (Class interf : interfaces) {
				try {
					return (T)getOrBuildObject((Class)interf, context);
				} catch (BuildingException exc) {

				}
			}
		}
		throw new BuildingException(
			"Unable to build the related object of " + clazz.getName() + ": " + Strings.join(", ", searchedClasses) + " have been searched without success"
		);
	}

	
	private  Class retrieveClass(
		List classNameItems,
		Collection notFoundClasses,
		String separator
	) throws ClassNotFoundException {
		Collection allClassNameCombinations = retrieveAllClassNameCombinations(classNameItems, separator);
		for (String className : allClassNameCombinations) {
			try {
				Class cls = (Class)Class.forName(className);
				if (Modifier.isAbstract(cls.getModifiers()) || cls.isInterface()) {
					notFoundClasses.add(className);
					continue;
				}
				return (Class)Class.forName(className);
			} catch (ClassNotFoundException exc) {
				notFoundClasses.add(className);
			}
		}
		throw new ClassNotFoundException();
	}


	Collection retrieveAllClassNameCombinations(List classNameItems, String separator) {
		List finalStringColl = new ArrayList();
		Set classNames = new LinkedHashSet();
		Collection> combinationsToBeReprocessed = new ArrayList<>(); 
		for (int i = classNameItems.size(); i > 0; i--) {
			List firstPartList = classNameItems.subList(0, i);
			String firstPart = Strings.join("", firstPartList);
			if (!firstPartList.isEmpty()) {
				finalStringColl.add(firstPart);
			}
			List secondPartList = classNameItems.subList(i, classNameItems.size());
			String secondPart = Strings.join("", secondPartList);
			if (!secondPartList.isEmpty()) {
				finalStringColl.add(secondPart);
			}
			classNames.add(Strings.join(separator, finalStringColl));
			if (secondPartList.size() > 1) {
				List toBeReprocessed = new ArrayList();
				String firstPartOfSecondPart = secondPartList.get(0);
				toBeReprocessed.add(firstPart + separator + firstPartOfSecondPart);
				toBeReprocessed.addAll(secondPartList.subList(1, secondPartList.size()));
				combinationsToBeReprocessed.add(toBeReprocessed);
			}
			finalStringColl.clear();
		}
		for (List toBeReprocessed : combinationsToBeReprocessed) {
			classNames.addAll(retrieveAllClassNameCombinations(toBeReprocessed, separator));
		}
		return classNames;
	}
	
	public static  F getObject(Class clazz, Map context) {
		F objectFound = (F) context.get(clazz.getName());
		if (objectFound != null) {
			return objectFound;
		} else {
			for (Object object : context.values()) {
				if (clazz.isAssignableFrom(object.getClass())) {
					return (F)object;
				}
			}
		}
		return null;
	}
	
	public static ObjectProvider get(Map context) {
		return (ObjectProvider)context.get(CLASS_NAME);
	}
	
	public static void putIfAbsent(Map context, Supplier objectProvider) {
		ObjectProvider objectProviderInMap = (ObjectProvider) context.get(CLASS_NAME);
		if (objectProviderInMap == null) {
			synchronized(context) {
				objectProviderInMap = (ObjectProvider)context.get(CLASS_NAME);
				if (objectProviderInMap == null) {
					context.put(CLASS_NAME, objectProvider.get());
				}				
			}
		}
	}
	

	public static void setExceptionHandler(Map context, ExceptionHandler exceptionHandler) {
		context.put("exceptionHandler", exceptionHandler);		
	}
	
	public static ExceptionHandler getExceptionHandler(Map context) {
		return (ExceptionHandler)context.get("exceptionHandler");
	}
	
	public static interface ExceptionHandler {		
		
		public  T handle(ObjectProvider objectProvider, Class clazz, Map context, BuildingException exc);
	}
	
	public static class BuildingException extends RuntimeException {

		private static final long serialVersionUID = -7606794206649872816L;

		public BuildingException(String message, Throwable cause) {
	        super(message, cause);
	    }
	    
	    public BuildingException(String message) {
	        super(message);
	    }

	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy