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

com.github.jy2.di.JyroscopeDi Maven / Gradle / Ivy

There is a newer version: 0.0.39
Show newest version
package com.github.jy2.di;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
//import java.lang.reflect.Parameter;
//import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.yaml.snakeyaml.Yaml;

import com.github.jy2.MasterClient;
import com.github.jy2.ParameterClient;
import com.github.jy2.ParameterListener;
import com.github.jy2.PubSubClient;
import com.github.jy2.Publisher;
import com.github.jy2.SlaveClient;
import com.github.jy2.Subscriber;
import com.github.jy2.di.annotations.Init;
import com.github.jy2.di.annotations.Inject;
import com.github.jy2.di.annotations.InstanceName;
import com.github.jy2.di.annotations.Publish;
import com.github.jy2.di.annotations.Repeat;
import com.github.jy2.di.annotations.RosTimeProvider;
import com.github.jy2.di.annotations.Subscribe;
import com.github.jy2.di.exceptions.CreationException;
import com.github.jy2.di.internal.ClassWithName;
import com.github.jy2.di.internal.GraphName;
import com.github.jy2.di.internal.Initializer;
import com.github.jy2.di.internal.InstanceWithName;
import com.github.jy2.di.internal.JyroscopeDiSingleton;
import com.github.jy2.di.internal.ParameterFromFileReference;
import com.github.jy2.di.internal.ParameterReference;
import com.github.jy2.di.internal.Repeater;
import com.github.jy2.di.internal.SubscriberRef;
import com.github.jy2.di.monitor.FileChangeMonitor;
import com.github.jy2.di.ros.TimeProvider;
import com.github.jy2.di.utils.JsonMapper;
import com.github.jy2.di.utils.YamlMapper;
import com.github.jy2.internal.DeleteSubscriber;
import com.github.jy2.log.Jy2DiLog;
import com.github.jy2.log.NodeNameManager;
import com.github.jy2.util.ExceptionUtil;

public class JyroscopeDi implements PubSubClient, DeleteSubscriber {

	private static final LogSeldom LOG = new Jy2DiLog(JyroscopeDi.class);

	private static final Yaml YAML = new Yaml();

	private HashMap parameters = new HashMap<>();
	private HashMap specialParameters = new HashMap<>();
	private HashMap remappings = new HashMap<>();

	private ArrayList initializers = new ArrayList<>();
	private ArrayList repeaters = new ArrayList<>();
	private HashMap repeatersMap = new HashMap<>();
	private ArrayList subscriberRefs = new ArrayList<>();
	private ArrayList parameterReferences = new ArrayList<>();
	private HashMap parameterReferenceMap = new HashMap<>();
	private ArrayList parameterFromFileReferences = new ArrayList<>();

	private HashMap instanceMap = new HashMap<>();
	private ArrayList instancesToInjectList = new ArrayList<>();

	private ArrayList> subscribers = new ArrayList<>();

	private JyroscopeDi parentSession;
	private ArrayList childSessions = new ArrayList<>();

	private String name;

	public HashSet publishedTopics = new HashSet<>();
	public HashSet subscribedTopics = new HashSet<>();

	private Object parameterListenerId;

	private FileChangeMonitor monitor;

	static {
		avoidSnakeyamlNpeUnderGraalVm();
	}

	private static void avoidSnakeyamlNpeUnderGraalVm() {
		// avoid snakeyaml NPE under graalvm in
		// org.yaml.snakeyaml.util.PlatformFeatureDetector.isRunningOnAndroid(PlatformFeatureDetector.java:25)
		if (System.getProperty("java.runtime.name") == null) {
			System.setProperty("java.runtime.name", "");
		}
	}

	public JyroscopeDi(String name, String[] args) throws CreationException {
		// parse special parameters
		for (int i = 0; i < args.length; i++) {
			if (!args[i].contains(":=")) {
				continue;
			}
			if (args[i].startsWith("__")) {
				int pos = args[i].indexOf(":=");
				String parameterName = args[i].substring(2, pos);
				String parameterValue = args[i].substring(pos + 2);
				specialParameters.put(parameterName, parameterValue);
			}
		}

		// parse name
		String specialParameterValue = specialParameters.get("name");
		if (specialParameterValue != null) {
			name = specialParameterValue;
		}
		this.name = graphNameOfName(name);

		// Needed by the loggers
		NodeNameManager.setNodeName(this.name);

		JyroscopeDiSingleton.initialize(specialParameters, this.name, this);

		// parse regular parameters and remappings
		for (int i = 0; i < args.length; i++) {
			if (!args[i].contains(":=")) {
				continue;
			}
			if (args[i].startsWith("_") && !args[i].startsWith("__")) {
				int pos = args[i].indexOf(":=");
				String parameterName = args[i].substring(1, pos);
				String parameterValue = args[i].substring(pos + 2);
				parameters.put(graphNameOfParameter("", parameterName), parameterValue);
			} else if (!args[i].startsWith("_")) {
				int pos = args[i].indexOf(":=");
				String remappingName = args[i].substring(0, pos);
				String remappingValue = args[i].substring(pos + 2);
				remappings.put(graphNameOfTopic("", remappingName), graphNameOfTopic("", remappingValue));
			}
		}

		// process parameters from command line
		for (Entry parameter : parameters.entrySet()) {
			try {
				setParameter(parameter.getKey(), parameter.getValue());
			} catch (IOException e) {
				LOG.error("Unable to set parameter on server" + parameter.getKey() + " " + parameter.getValue(), e);
			}
		}
	}

	private JyroscopeDi(JyroscopeDi di) {
		parentSession = di;

		// copy from parent
		name = di.name;
		remappings = di.remappings;
	}

	public synchronized JyroscopeDi createChildSession() {
		JyroscopeDi di = new JyroscopeDi(this);
		childSessions.add(di);
		return di;
	}

	private void detach(JyroscopeDi di) {
		childSessions.remove(di);
	}

	public String getName() {
		return name;
	}

	public Object getMemberName() {
		return JyroscopeDiSingleton.getMemberName();
	}

	public TimeProvider getTimeProvider() {
		return JyroscopeDiSingleton.TIME_PROVIDER;
	}

	public synchronized void start() throws CreationException {

		// add jyroscope di instance
		instanceMap.put(new ClassWithName(this.getClass(), ""), this);
		instancesToInjectList.add(new InstanceWithName(this, ""));

		// inject dependencies
		injectDependencies();

		// get all the parameters
		for (ParameterReference ref : parameterReferences) {
			processParameterReference(ref);
		}

		// get all the parameters from file
		loadParametersFromFile();

		if (!parameterFromFileReferences.isEmpty()) {
			Subscriber subscriber = createSubscriber("reload_parameters", Boolean.class);
			Publisher publisher = createPublisher("parameters_reloaded", Boolean.class);
			subscriber.addMessageListener(msg -> {
				LOG.info("Reloading parameters from file");
				loadParametersFromFile();
				publisher.publish(true);
			});
			// install file monitor
			for (ParameterFromFileReference ref : parameterFromFileReferences) {
				if (ref.watch) {
					Object value = null;
					try {
						value = getParameter(ref.parameterName);
					} catch (IOException e1) {
						LOG.error("Exception while getting parameter " + ref.parameterName, e1);
					}
					if (value == null) {
						value = ref.defaultValue;
					}
					String fileName = value.toString();
					if (fileName.isEmpty()) {
						LOG.info("Empty parameter: " + ref.parameterName + ", skipping registering file monitor");
						continue;
					}
					if (monitor == null) {
						monitor = new FileChangeMonitor();
					}
					try {
						monitor.addFileListener(fileName, new Runnable() {
							@Override
							public void run() {
								LOG.info("Noticed file change, updating parameter from file");
								processParameterFromFileReference(ref);
								publisher.publish(true);
							}
						});
					} catch (IOException e) {
						LOG.error("Exception caught while adding file listener " + fileName, e);
					}
				}
			}
		}

		// add callback on parameter change
		registerParameterChangeCallback();

		// start all initializers
		for (Initializer initializer : initializers) {
			executeInitializer(initializer);
		}

		// start all repeaters
		for (Repeater repeater : repeaters) {
			startRepeater(repeater);
		}

		// register all the subscribers
		for (SubscriberRef subscriber : subscriberRefs) {
			subscriber.start();
		}
	}

	private synchronized void loadParametersFromFile() {
		for (ParameterFromFileReference ref : parameterFromFileReferences) {
			processParameterFromFileReference(ref);
		}
	}

	public synchronized void shutdown() {
		// detach from parent session
		if (parentSession != null) {
			parentSession.detach(this);
		}

		// clear initializers references
		initializers.clear();

		// shutdown all repeaters
		for (Repeater repeater : repeaters) {
			repeater.shutdown();
		}
		repeaters.clear();
		repeatersMap.clear();

		// shutdown all subscriber refs (created from annotations)
		for (SubscriberRef subscriber : subscriberRefs) {
			subscriber.shutdown();
		}
		subscriberRefs.clear();

		// shutdown all subscribers (created using api)
		for (Subscriber subscriber : subscribers) {
			subscriber.removeAllMessageListeners();
		}
		subscribers.clear();

		// shutdown parameter references
		parameterReferences.clear();
		parameterReferenceMap.clear();

		// stop file monitor
		if (monitor != null) {
			monitor.removeAllListeners();
			monitor = null;
		}

		// remove parameter change callback
		if (parameterListenerId != null) {
			try {
				JyroscopeDiSingleton.jy2.getParameterClient().removeParameterListener(parameterListenerId);
			} catch (IOException e) {
				LOG.warn("Unable to remove parameter client", e);
			}
			parameterListenerId = null;
		}

		// clear instance references
		instanceMap.clear();
		instancesToInjectList.clear();

		// shutdown all child sessions
		for (JyroscopeDi session : childSessions) {
			session.shutdown();
		}
		childSessions.clear();
	}

	/**
	 * Returns logger wrapped with /rosout log publisher.
	 */
	public static LogSeldom getLog(String name) {
		return new Jy2DiLog(name);
	}

	/**
	 * Returns logger wrapped with /rosout log publisher.
	 */
	public static LogSeldom getLog(Class clazz) {
		return new Jy2DiLog(clazz);
	}

	/**
	 * Returns logger wrapped with /rosout log publisher that gets caller class
	 * name.
	 */
	public static LogSeldom getLog() {
		Throwable dummyException = new Throwable();
		StackTraceElement[] locations = dummyException.getStackTrace();
		String cname = "unknown";
		if (locations != null && locations.length > 1) {
			StackTraceElement caller = locations[1];
			cname = caller.getClassName();
		}
		return new Jy2DiLog(cname);
	}

	public  T create(Class clazz) throws CreationException {
		return create(clazz, "", true);
	}

	public  T inject(T object) throws CreationException {
		return inject(object, "", true);
	}

	public  T create(Class clazz, boolean singleton) throws CreationException {
		return create(clazz, "", singleton);
	}

	public  T inject(T object, boolean singleton) throws CreationException {
		return inject(object, "", singleton);
	}

	public  T create(Class clazz, String instanceName) throws CreationException {
		return create(clazz, instanceName, true);
	}

	public  T inject(T object, String instanceName) throws CreationException {
		return inject(object, instanceName, true);
	}

	/**
	 * Creates new object of the given class and injects the properties according to
	 * the annotations. Requires connectToRemoteMaster to be called before.
	 */
	public  T create(Class clazz, String instanceName, boolean singleton) throws CreationException {
		try {
			return inject(clazz.newInstance(), instanceName, singleton);
		} catch (InstantiationException | IllegalAccessException e) {
			LOG.error("Could not create class " + clazz.toGenericString(), e);
			throw new CreationException("Could not create class " + clazz.toGenericString(), e);
		}
	}

	/**
	 * Injects the properties according to the annotations. Requires
	 * connectToRemoteMaster to be called before.
	 */
	public  T inject(T object, String instanceName, boolean singleton) throws CreationException {
		ClassWithName classWithName = new ClassWithName(object.getClass(), instanceName);
		Class clazz = object.getClass();
		if (singleton) {
			// verify there is only one singleton
			JyroscopeDi di = this;
			while (di != null) {
				if (di.instanceMap.get(classWithName) != null) {
					throw new IllegalArgumentException("There can be only one singleton");
				}
				di = di.parentSession;
			}
		}
		try {
			// for each field
			for (Field field : clazz.getDeclaredFields()) {
				injectTimeProvider(field, object, clazz);
				injectInstanceName(field, object, instanceName);
				collectParameters(field, object, instanceName);
				collectParametersFromFile(field, object, instanceName);
				injectPublishers(field, object, instanceName);
			}

			// for each method
			for (Method method : clazz.getDeclaredMethods()) {
				collectInitializers(method, object);
				collectRepeaters(method, object);
				collectSubscriberRefs(method, object, instanceName);
			}

			// cache the instances for dependency injection
			if (singleton) {
				instanceMap.put(classWithName, object);
			}
			instancesToInjectList.add(new InstanceWithName(object, instanceName));
		} catch (IllegalAccessException e) {
			throw new CreationException("Exception while creating " + clazz.toString(), e);
		}
		return object;
	}

	public synchronized void wakeupRepeater(Object object, String name) {
		Thread thread = repeatersMap.get(new InstanceWithName(object, name));
		if (thread == null) {
			LOG.errorSeldom("Cannot wakeup repeater, cannot find " + object.getClass().getCanonicalName() + " " + name);
		} else {
			thread.interrupt();
		}
	}

	public  Publisher createPublisher(String topicName, Class topicType, boolean isLatched, int queueSize) {
		String newTopicName = graphNameOfTopic("", topicName);
		String remappedTopicName = remappings.get(newTopicName);
		if (remappedTopicName != null) {
			newTopicName = remappedTopicName;
		}
//		Class type2 = singleton.topicTypeMap.get(topicName);
//		if (type2 == null) {
//			singleton.topicTypeMap.put(newTopicName, type);
//		} else if (!type.equals(type2)) {
//			throw new RuntimeException("Type mismatch in topic " + topicName + ", new type: " + type.getName()
//					+ ", existing type:" + type2.getName());
//		}
//
//		if (!JyroscopeDi.ALWAYS_USE_RELIABLE_TOPIC) {
//			IsReliable value = singleton.topicIsReliableMap.get(topicName);
//			boolean existingReliable = (value == IsReliable.TRUE);
//			if (value != null) {
//				if (isReliable != existingReliable) {
//					throw new RuntimeException("isReliable mismatch in topic " + topicName + ", new: " + isReliable
//							+ ", existing:" + existingReliable);
//				}
//			} else {
//				singleton.topicIsReliableMap.put(topicName, isReliable ? IsReliable.TRUE : IsReliable.FALSE);
//			}
//		}
//
//		singleton.topicsSet.add(newTopicName);
//		singleton.nodePublishersMap.put(this.name, topicName);
//		return new Publisher<>(singleton.hzInstance, singleton.latchedMap, newTopicName, false, isReliable);
		publishedTopics.add(topicName);
		return JyroscopeDiSingleton.jy2.createPublisher(newTopicName, topicType, isLatched, queueSize);
	}

	/**
	 * Type = null subscribes to any type.
	 */
	@Override
	public synchronized  Subscriber createSubscriber(String topicName, Class topicType, int queueLength,
			int maxExecutionTime, boolean isReliable) {
		String newTopicName = graphNameOfTopic("", topicName);
		String remappedTopicName = remappings.get(newTopicName);
		if (remappedTopicName != null) {
			newTopicName = remappedTopicName;
		}
//		// when type == null subscribe to any type (useful for hz tool)
//		if (type != null) {
//			Class type2 = singleton.topicTypeMap.get(topicName);
//			if (type2 == null) {
//				singleton.topicTypeMap.put(newTopicName, type);
//			} else if (!type.equals(type2)) {
//				throw new RuntimeException("Type mismatch in topic " + topicName + ", new type: " + type.getName()
//						+ ", existing type:" + type2.getName());
//			}
//		}
//
//		if (!JyroscopeDi.ALWAYS_USE_RELIABLE_TOPIC) {
//			IsReliable value = singleton.topicIsReliableMap.get(topicName);
//			boolean existingReliable = (value == IsReliable.TRUE);
//			if (value != null) {
//				if (isReliable != existingReliable) {
//					throw new RuntimeException("isReliable mismatch in topic " + topicName + ", new: " + isReliable
//							+ ", existing:" + existingReliable);
//				}
//			} else {
//				singleton.topicIsReliableMap.put(topicName, isReliable ? IsReliable.TRUE : IsReliable.FALSE);
//			}
//		}
//
//		singleton.topicsSet.add(newTopicName);
//		singleton.nodeSubscribersMap.put(this.name, topicName);
//		return new Subscriber<>(singleton.hzInstance, singleton.latchedMap, newTopicName, queueLength, maxExecutionTime,
//				isReliable, LOG);
		subscribedTopics.add(topicName);
		Subscriber subscriber = JyroscopeDiSingleton.jy2.createSubscriber(newTopicName, topicType, queueLength,
				maxExecutionTime, isReliable, this);
		subscribers.add(subscriber);
		return subscriber;
	}

	public synchronized  void deleteSubscriber(Subscriber subscriber) {
		subscriber.removeAllMessageListeners();
		subscribers.remove(subscriber);
	}

	public  T getInstance(Class type) {
		return getInstance(type, "", true);
	}

	public  T getInstance(Class type, boolean singleton) {
		return getInstance(type, "", singleton);
	}

	@SuppressWarnings("unchecked")
	public  T getInstance(Class type, String instanceName, boolean singleton) {
		try {
			ClassWithName c = new ClassWithName(type, instanceName);
			return (T) getInstance(c, singleton);
		} catch (CreationException e) {
			throw new RuntimeException(
					"Problem with creating instance: " + type.getCanonicalName() + ", instanceName: " + instanceName,
					e);
		}
	}

	public MasterClient getMasterClient() {
		return JyroscopeDiSingleton.jy2.getMasterClient();
	}

	public ParameterClient getParameterClient() {
		return JyroscopeDiSingleton.jy2.getParameterClient();
	}

	@Override
	public SlaveClient getSlaveClient(String name) {
		return JyroscopeDiSingleton.jy2.getSlaveClient(name);
	}

	private void executeInitializer(Initializer initializer) {
		makeAccessible(initializer.method);
		long before = System.currentTimeMillis();
		try {
			initializer.method.invoke(initializer.object);
		} catch (Exception e) {
			ExceptionUtil.rethrowErrorIfCauseIsError(e);
			LOG.error("Exception caught while calling node initializer " + initializer.method.toGenericString(), e);
		}
		long delta = System.currentTimeMillis() - before;
		if (delta > initializer.init.maxExecutionTime() && initializer.init.maxExecutionTime() > 0) {
			LOG.warn("Initializer execution time " + delta + " exceeded threshold "
					+ initializer.init.maxExecutionTime() + " in method " + initializer.method.toGenericString());
		}
	}

	private void startRepeater(Repeater repeater) {
		Object object = repeater.object;
		Method method = repeater.method;
		makeAccessible(method);
		Repeat repeat = repeater.repeat;
		boolean isDelay = repeat.delay() != 0;
		boolean isInterval = repeat.interval() != 0;
		repeater.thread = new Thread(new Runnable() {
			@Override
			public void run() {
				int count = 0;
				long start = System.currentTimeMillis();
				while ((repeat.count() == 0 || count < repeat.count()) && !repeater.shutdown) {
					count++;
					try {
						long before = System.currentTimeMillis();
						Object result = method.invoke(object);
						long delta = System.currentTimeMillis() - before;
						if (delta > repeater.repeat.maxExecutionTime() && repeater.repeat.maxExecutionTime() > 0) {
							LOG.warn("Repeater execution time " + delta + " exceeded threshold "
									+ repeater.repeat.maxExecutionTime() + " in method " + method.toGenericString());
						}

						// Check if it returned false
						if (result != null) {
							if (!(Boolean) result) {
								break;
							}
						}
					} catch (Exception e) {
						ExceptionUtil.rethrowErrorIfCauseIsError(e);
						LOG.error("Exception caught while calling repeater " + method.toGenericString(), e);
					}
					try {
						if (isDelay) {
							Thread.sleep(repeat.delay());
						} else if (isInterval) {
							long now = System.currentTimeMillis();
							long sleep = repeat.interval() - (now - start);
							start += repeat.interval();
							if (sleep > 0) {
								Thread.sleep(sleep);
							}
						}
					} catch (InterruptedException ie) {
						// thread was woken up, restart the counter
						start = System.currentTimeMillis();
					}
				}
			}
		}, "Repeater-" + method.toString());
		repeater.thread.start();
		String name = repeat.name();
		if (name != null && !name.isEmpty()) {
			repeatersMap.put(new InstanceWithName(repeater.object, name), repeater.thread);
		}
	}

	private  void injectPublishers(Field field, T object, String instanceName)
			throws IllegalAccessException, IllegalArgumentException, CreationException {
		Publish publish = field.getAnnotation(Publish.class);
		if (publish != null) {
			verifyNonStatic(field);
			makeAccessible(field);

			Publisher publisher = createPublisher(publish, field, instanceName);
			field.set(object, publisher);
		}
	}

	private  void collectParametersFromFile(Field field, T object, String instanceName) {
		com.github.jy2.di.annotations.ParameterFromFile parameter = field
				.getAnnotation(com.github.jy2.di.annotations.ParameterFromFile.class);
		if (parameter != null) {
			makeAccessible(field);

			String parameterName = graphNameOfParameter(instanceName, parameter.name());
			ParameterFromFileReference ref = new ParameterFromFileReference(parameterName, parameter.defaultValue(),
					parameter.watch(), object, field);
			parameterFromFileReferences.add(ref);
		}
	}

	private  void collectParameters(Field field, T object, String instanceName)
			throws CreationException, IllegalAccessException {
		// inject parameters
		com.github.jy2.di.annotations.Parameter parameter = field
				.getAnnotation(com.github.jy2.di.annotations.Parameter.class);
		if (parameter != null) {
			verifyNonStatic(field);
			makeAccessible(field);

			String parameterName = graphNameOfParameter(instanceName, parameter.value());
			ParameterReference ref = new ParameterReference(parameterName, object, field);
			parameterReferences.add(ref);
			parameterReferenceMap.put(ref.parameterName, ref);
		}
	}

	private  void injectTimeProvider(Field field, T object, Class clazz)
			throws IllegalAccessException, CreationException, IllegalArgumentException {
		// inject ros time provider
		RosTimeProvider rosLog = field.getAnnotation(RosTimeProvider.class);
		if (rosLog != null) {
			makeAccessible(field);
			field.set(object, JyroscopeDiSingleton.TIME_PROVIDER);
		}
	}

	private  void injectInstanceName(Field field, T object, String name)
			throws IllegalAccessException, IllegalArgumentException, CreationException {
		// inject instance name
		InstanceName instanceName = field.getAnnotation(InstanceName.class);
		if (instanceName != null) {
			makeAccessible(field);
			field.set(object, name);
		}
	}

	private void injectDependencies() throws CreationException {
		for (int i = 0; i < instancesToInjectList.size(); i++) {
			InstanceWithName object = instancesToInjectList.get(i);
			Class clazz = object.instance.getClass();
			try {
				// for each field
				for (Field field : clazz.getDeclaredFields()) {
					Inject inject = field.getAnnotation(Inject.class);
					if (inject != null) {
						Class type = field.getType();
						String instanceName = inject.instance();
						if (!instanceName.startsWith("/")) {
							if (object.name.isEmpty() || instanceName.isEmpty()) {
								instanceName = object.name + instanceName;
							} else {
								instanceName = object.name + "/" + instanceName;
							}
						}
						// when injecting RosJavaDi always use one instance
						if (type.equals(this.getClass())) {
							instanceName = "";
						}
						ClassWithName c = new ClassWithName(type, instanceName);
						boolean singleton = inject.singleton();
						Object instance = getInstance(c, singleton);
						makeAccessible(field);
						field.set(object.instance, instance);
					}
				}
			} catch (IllegalAccessException e) {
				throw new CreationException("Exception while injecting dependencies " + clazz.toString(), e);
			}
		}
		instancesToInjectList.clear();
	}

	private Object getInstance(ClassWithName c, boolean singleton) throws CreationException {
		if (singleton) {
			JyroscopeDi di = this;
			while (di != null) {
				Object object = di.instanceMap.get(c);
				if (object != null) {
					return object;
				}
				di = di.parentSession;
			}
		}
		if (!c.name.isEmpty()) {
			return create(c.type, c.name, singleton);
		} else {
			return create(c.type, singleton);
		}
	}

	private  void collectSubscriberRefs(Method method, T object, String instanceName) throws CreationException {
		// create subscribers
		Subscribe subscribe = method.getAnnotation(Subscribe.class);
		if (subscribe != null) {
			SubscriberRef subscriber = createSubscriberRef(subscribe, object, method, instanceName);
			subscriberRefs.add(subscriber);
		}
	}

	private  SubscriberRef createSubscriberRef(Subscribe subscribe, T object, Method method, String instanceName)
			throws CreationException {
		java.lang.reflect.Parameter[] parameters = method.getParameters();
		if (parameters.length != 1) {
			throw new CreationException(
					"Subscriber at " + method.toGenericString() + " must have exactly one parameter");
		}
		java.lang.reflect.Parameter parameter = parameters[0];
		final Class type = parameter.getType();

		int queueLenght = subscribe.queueSize();
		int timeout = subscribe.timeout();
		int maxExecutionTime = subscribe.maxExecutionTime();

		String topicName = graphNameOfTopic(instanceName, subscribe.value());
		String remappedTopicName = remappings.get(topicName);
		if (remappedTopicName != null) {
			topicName = remappedTopicName;
		}

		// verify ros-correctness of name
		GraphName.verify(topicName);

//		Class type2 = singleton.topicTypeMap.get(topicName);
//		if (type2 == null) {
//			singleton.topicTypeMap.put(topicName, type);
//		} else if (!type.equals(type2)) {
//			throw new RuntimeException("Type mismatch in topic " + topicName + ", new type: " + type.getName()
//					+ ", existing type: " + type2.getName());
//		}
//
//		if (!JyroscopeDi.ALWAYS_USE_RELIABLE_TOPIC) {
//			boolean newReliable = subscribe.reliable();
//			IsReliable value = singleton.topicIsReliableMap.get(topicName);
//			boolean existingReliable = (value == IsReliable.TRUE);
//			if (value != null) {
//				if (newReliable != existingReliable) {
//					throw new RuntimeException("isReliable mismatch in topic " + topicName + ", new: " + newReliable
//							+ ", existing:" + existingReliable);
//				}
//			} else {
//				singleton.topicIsReliableMap.put(topicName, newReliable ? IsReliable.TRUE : IsReliable.FALSE);
//			}
//		}
//
//		singleton.topicsSet.add(topicName);
//		singleton.nodeSubscribersMap.put(this.name, topicName);

		verifyNonStatic(method);
		makeAccessible(method);

		subscribedTopics.add(topicName);
		return new SubscriberRef(JyroscopeDiSingleton.jy2, object, method, topicName, type, queueLenght, timeout,
				maxExecutionTime, LOG);
	}

	private  void collectRepeaters(Method method, T object) throws CreationException {
		Repeat repeat = method.getAnnotation(Repeat.class);
		if (repeat != null) {
			verifyNonStatic(method);
			makeAccessible(method);
			repeaters.add(new Repeater(object, method, repeat));
		}
	}

	private  void collectInitializers(Method method, T object) throws CreationException {
		Init init = method.getAnnotation(Init.class);
		if (init != null) {
			verifyNonStatic(method);
			makeAccessible(method);
			initializers.add(new Initializer(object, method, init));
		}
	}

	private Publisher createPublisher(Publish publish, Field field, String instanceName) {
		Type type = field.getGenericType();
		Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments();

		Class topicType = getGenericParameterType(typeArgs[0]);
		if (topicType == null) {
			throw new UnsupportedClassVersionError(
					"Unrecognized type parameter for publisher at " + field.toGenericString());
		}

		boolean isLatched = publish.latched();

		String topicName = graphNameOfTopic(instanceName, publish.value());
		String remappedTopicName = remappings.get(topicName);
		if (remappedTopicName != null) {
			topicName = remappedTopicName;
		}

//		Class type2 = singleton.topicTypeMap.get(topicName);
//		if (type2 == null) {
//			singleton.topicTypeMap.put(topicName, topicType);
//		} else if (!topicType.equals(type2)) {
//			throw new RuntimeException("Type mismatch in topic " + topicName + ", new type: " + topicType.getName()
//					+ ", existing type:" + type2.getName());
//		}
//
//		if (!JyroscopeDi.ALWAYS_USE_RELIABLE_TOPIC) {
//			boolean newReliable = publish.reliable();
//			IsReliable value = singleton.topicIsReliableMap.get(topicName);
//			boolean existingReliable = (value == IsReliable.TRUE);
//			if (value != null) {
//				if (newReliable != existingReliable) {
//					throw new RuntimeException("isReliable mismatch in topic " + topicName + ", new: " + newReliable
//							+ ", existing:" + existingReliable);
//				}
//			} else {
//				singleton.topicIsReliableMap.put(topicName, newReliable ? IsReliable.TRUE : IsReliable.FALSE);
//			}
//		}
//
//		singleton.topicsSet.add(topicName);
//		singleton.nodePublishersMap.put(this.name, topicName);

		publishedTopics.add(topicName);
		return JyroscopeDiSingleton.jy2.createPublisher(topicName, topicType, isLatched, publish.queueSize());
	}

	private Class getGenericParameterType(Type param) {
		Class topicType = null;
		if (param instanceof Class) {
			topicType = (Class) param;
		} else if (param instanceof ParameterizedType) {
			Type rawType = ((ParameterizedType) param).getRawType();
			if (rawType instanceof Class) {
				topicType = (Class) rawType;
			}
		}
		return topicType;
	}

	private void makeAccessible(Field field) {
		if (!Modifier.isPublic(field.getModifiers())) {
			field.setAccessible(true);
		}
	}

	private void makeAccessible(Method method) {
		if (!Modifier.isPublic(method.getModifiers())) {
			method.setAccessible(true);
		}
	}

	private void verifyNonStatic(Field field) throws CreationException {
		if (Modifier.isStatic(field.getModifiers())) {
			throw new CreationException("Field " + field.toGenericString() + " must be non-static");
		}
	}

	private void verifyNonStatic(Method method) throws CreationException {
		if (Modifier.isStatic(method.getModifiers())) {
			throw new CreationException("Method " + method.toGenericString() + " must be non-static");
		}
	}

	private String graphNameOfName(String name) {
		if (name.startsWith("/")) {
			return name;
		} else {
			return "/" + name;
		}
	}

	private String graphNameOfTopic(String instanceName, String name) {
		if (name.startsWith("/")) {
			return name;
		} else {
			if (instanceName.isEmpty()) {
				return "/" + this.name + "/" + name;
			} else {
				return "/" + this.name + "/" + instanceName + "/" + name;

			}
		}
	}

	private String graphNameOfParameter(String instanceName, String name) {
		if (!name.endsWith("/")) {
			name = name + "/";
		}
		if (name.startsWith("/")) {
			return name;
		} else {
			if (instanceName.isEmpty()) {
				return this.name + "/" + name;
			} else {
				return this.name + "/" + instanceName + "/" + name;

			}
		}
	}

	private void processParameterReference(ParameterReference ref) {
		try {
			Object value = getParameter(ref.parameterName);
			if (value == null) {
				LOG.info("Unset parameter: " + ref.parameterName + ", getting default value");
				publishParameter(ref.object, ref.field, ref.parameterName);
			} else {
				setParameterValueFromServer(ref.object, ref.field, ref.parameterName, value);
			}
		} catch (IOException e) {
			LOG.error("Unable to get parameter: " + ref.parameterName + ", getting default value");
		}
	}

	private void processParameterFromFileReference(ParameterFromFileReference ref) {
		Class type = ref.field.getType();
		try {
			Object value = getParameter(ref.parameterName);
			if (value == null) {
				LOG.info("Unset parameter: " + ref.parameterName + ", getting default value: " + ref.defaultValue);
				value = ref.defaultValue;
			}

			String fileName = value.toString();
			if (fileName.isEmpty()) {
				LOG.info("Empty parameter: " + ref.parameterName + ", skipping reading parameter from file");
				return;
			}

			Object obj;
			if (fileName.endsWith(".json")) {
				// read json
				obj = JsonMapper.map(new File(fileName), type);
			} else {
				// read yaml
				obj = YamlMapper.map(new File(fileName), type);
			}
			ref.field.set(ref.object, obj);
		} catch (Exception e) {
			LOG.error("Cannot set parameter " + ref.field.getName() + " in " + ref.object.getClass().getCanonicalName()
					+ ", type " + type, e);
		}
	}

	private Object getParameter(String name) throws IOException {
		return JyroscopeDiSingleton.jy2.getParameterClient().getParameter(name);
	}

	private  void setParameter(String name, T value) throws IOException {
		JyroscopeDiSingleton.jy2.getParameterClient().setParameter(name, value);
	}

	private  void publishParameter(T object, Field field, String parameterName) {
		try {
			Object value = field.get(object);
			Class type = field.getType();

			if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type)) {
				if (value != null) {
					setParameter(parameterName, (boolean) value);
				} else {
					setParameter(parameterName, "");
				}
			} else if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) {
				if (value != null) {
					setParameter(parameterName, (int) value);
				} else {
					setParameter(parameterName, "");
				}
			} else if (Double.class.isAssignableFrom(type) || double.class.isAssignableFrom(type)) {
				if (value != null) {
					setParameter(parameterName, (double) value);
				} else {
					setParameter(parameterName, "");
				}
			} else if (List.class.isAssignableFrom(type)) {
				if (value != null) {
					setParameter(parameterName, YAML.dump(value));
				} else {
					setParameter(parameterName, "[]");
				}
			} else if (Map.class.isAssignableFrom(type)) {
				if (value != null) {
					setParameter(parameterName, YAML.dump(value));
				} else {
					setParameter(parameterName, "{}");
				}
			} else { // if (String.class.isAssignableFrom(type)) {
				if (value != null) {
					setParameter(parameterName, value.toString());
				} else {
					setParameter(parameterName, "");
				}
			}
		} catch (IllegalArgumentException | IllegalAccessException | IOException e) {
			LOG.info("Error publishing parameter value: " + parameterName, e);
		}
	}

	private  void setParameterValueFromServer(T object, Field field, String name, Object value) {
		Class type = field.getType();
		try {
			if (Boolean.class.isAssignableFrom(type) || boolean.class.isAssignableFrom(type)) {
				if (Boolean.class.isAssignableFrom(value.getClass())
						|| boolean.class.isAssignableFrom(value.getClass())) {
					field.set(object, (boolean) value);
				} else {
					field.set(object, Boolean.parseBoolean(value.toString()));
				}
			} else if (Integer.class.isAssignableFrom(type) || int.class.isAssignableFrom(type)) {
				// NOTE: bug was here. int a = (int)(Double)b does not compile, int a =
				// (int)(double)b does compile
				if (Integer.class.isAssignableFrom(value.getClass()) || int.class.isAssignableFrom(value.getClass())) {
					field.set(object, (int) value);
				} else if (Long.class.isAssignableFrom(value.getClass())
						|| long.class.isAssignableFrom(value.getClass())) {
					field.set(object, (int) (long) value);
				} else if (Double.class.isAssignableFrom(value.getClass())
						|| double.class.isAssignableFrom(value.getClass())) {
					field.set(object, (int) (double) value);
				} else {
					field.set(object, (int) Double.parseDouble(value.toString()));
				}
			} else if (Double.class.isAssignableFrom(type) || double.class.isAssignableFrom(type)) {
				if (Integer.class.isAssignableFrom(value.getClass()) || int.class.isAssignableFrom(value.getClass())
						|| Double.class.isAssignableFrom(value.getClass())
						|| double.class.isAssignableFrom(value.getClass())) {
					field.set(object, (double) value);
				} else {
					field.set(object, Double.parseDouble(value.toString()));
				}
			} else if (String.class.isAssignableFrom(type)) {
				field.set(object, value.toString());
			} else if (List.class.isAssignableFrom(type)) {
				field.set(object, YAML.loadAs(value.toString(), ArrayList.class));
			} else if (Map.class.isAssignableFrom(type)) {
				field.set(object, YAML.loadAs(value.toString(), HashMap.class));
			}
		} catch (NumberFormatException e) {
			LOG.error("Cannot set parameter " + field.getName() + " in " + object.getClass().getCanonicalName()
					+ ", wrong number format " + type + ", parameter: " + name + " " + value.toString(), e);
		} catch (IllegalArgumentException e) {
			LOG.error("Cannot set parameter " + field.getName() + " in " + object.getClass().getCanonicalName()
					+ ", incompatible types " + type + ", parameter: " + name + " " + value.toString()
					+ ", for exmple consider maing it a List not an ArrayList", e);
		} catch (ClassCastException e) {
			LOG.error("Cannot set parameter " + field.getName() + " in " + object.getClass().getCanonicalName()
					+ ", incompatible types " + type + ", parameter: " + name + " " + value.toString()
					+ ", for example consider maing it a List not an ArrayList", e);
		} catch (IllegalAccessException e) {
			LOG.error("Cannot set parameter " + field.getName() + " in " + object.getClass().getCanonicalName()
					+ ", illegal access " + type + ", parameter: " + name + " " + value.toString(), e);
		}

	}

	private void registerParameterChangeCallback() {
		try {
			parameterListenerId = JyroscopeDiSingleton.jy2.getParameterClient().addParameterListener("/",
					new ParameterListener() {
						@Override
						public void onParameterUpdated(String name, Object value) {
							onParameterChanged(name, value);
						}
					});
		} catch (IOException e) {
			LOG.error("Unable to register parameter change callback", e);
		}
	}

	private synchronized void onParameterChanged(String name, Object value) {
		LOG.debug("Parameter callback: " + name + ":=" + value.toString());
		ParameterReference ref = parameterReferenceMap.get(name);
		if (ref == null) {
			LOG.debug("Unknown parameter, skipping: " + name);
		} else {
			setParameterValueFromServer(ref.object, ref.field, name, value);
		}
	}

	public String getRemappedTopicName(String topicName) {
		String newTopicName = graphNameOfTopic("", topicName);
		String remappedTopicName = remappings.get(newTopicName);
		if (remappedTopicName != null) {
			newTopicName = remappedTopicName;
		}
		return newTopicName;
	}

	public String getCommandLineParameter(String parameterName) {
		if (parameters != null) {
			return parameters.get(graphNameOfParameter("", parameterName));
		} else {
			return null;
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy