<commit_before>/******************************************************************************
 *                                                                            *
 *                    Copyright 2017 Subterranean Security                    *
 *                                                                            *
 *  Licensed under the Apache License, Version 2.0 (the "License");           *
 *  you may not use this file except in compliance with the License.          *
 *  You may obtain a copy of the License at                                   *
 *                                                                            *
 *      http://www.apache.org/licenses/LICENSE-2.0                            *
 *                                                                            *
 *  Unless required by applicable law or agreed to in writing, software       *
 *  distributed under the License is distributed on an "AS IS" BASIS,         *
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *
 *  See the License for the specific language governing permissions and       *
 *  limitations under the License.                                            *
 *                                                                            *
 *****************************************************************************/
package com.sandpolis.core.instance;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sandpolis.core.instance.ConfigConstant.logging;
import com.sandpolis.core.instance.idle.IdleLoop;
import com.sandpolis.core.proto.util.Platform.Instance;
import com.sandpolis.core.proto.util.Platform.InstanceFlavor;
import com.sandpolis.core.proto.util.Result.Outcome;
import com.sandpolis.core.util.ProtoUtil;

/**
 * {@link MainDispatch} allows the instance's main method to configure
 * initialization tasks before they are sequentially executed by this class.
 * Idle tasks and shutdown tasks are also managed by this class.
 * 
 * @author cilki
 * @since 5.0.0
 */
public final class MainDispatch {

	public static final Logger log = LoggerFactory.getLogger(MainDispatch.class);

	/**
	 * A configurable list of tasks that initialize the instance.
	 */
	private static List<Task> tasks = new ArrayList<>();

	/**
	 * A configurable list of tasks that are executed on instance shutdown.
	 */
	private static List<Task> shutdown = new LinkedList<>();

	/**
	 * A {@link Thread} that runs idle tasks in the background.
	 */
	private static IdleLoop idle;

	/**
	 * The instance's main {@link Class}.
	 */
	private static Class<?> main = MainDispatch.class;

	/**
	 * The instance's {@link Instance} type.
	 */
	private static Instance instance;

	/**
	 * The instance's {@link InstanceFlavor} type.
	 */
	private static InstanceFlavor flavor;

	/**
	 * Get the {@link Class} that was dispatched.
	 * 
	 * @return The dispatched {@link Class} or {@code MainDispatch.class} if
	 *         {@link #dispatch} has not been called
	 */
	public static Class<?> getMain() {
		return main;
	}

	/**
	 * Get the {@link Instance} type.
	 * 
	 * @return The dispatched {@link Instance} or {@code null} if {@link #dispatch}
	 *         has not been called
	 */
	public static Instance getInstance() {
		return instance;
	}

	/**
	 * Get the {@link InstanceFlavor} type.
	 * 
	 * @return The dispatched {@link InstanceFlavor} or {@code null} if
	 *         {@link #dispatch} has not been called
	 */
	public static InstanceFlavor getInstanceFlavor() {
		return flavor;
	}

	/**
	 * Get the {@link IdleLoop}.
	 * 
	 * @return The registered {@link IdleLoop} or {@code null} if one has not been
	 *         registered
	 */
	public static IdleLoop getIdleLoop() {
		return idle;
	}

	/**
	 * Invokes the instance's main method (which should register initialization
	 * tasks with {@link MainDispatch}) and then initializes the instance.
	 * 
	 * @param main     The {@link Class} which contains the {@code main} to invoke
	 * @param args     The arguments to be passed to the {@code main}
	 * @param instance The instance's {@link Instance}
	 * @param flavor   The instance's {@link InstanceFlavor}
	 */
	public static void dispatch(Class<?> main, String[] args, Instance instance, InstanceFlavor flavor) {
		if (MainDispatch.main != MainDispatch.class)
			throw new IllegalStateException("Dispatch cannot be called more than once");

		MainDispatch.main = Objects.requireNonNull(main);
		MainDispatch.instance = Objects.requireNonNull(instance);
		MainDispatch.flavor = Objects.requireNonNull(flavor);

		long timestamp = System.currentTimeMillis();

		// Pass main arguments to the Config class
		Config.setArguments(args);

		// Setup exception handler
		Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
			log.error("An unexpected exception has occurred", throwable);
		});

		// Setup shutdown hook
		Runtime.getRuntime().addShutdownHook(new Thread(() -> {
			shutdown.forEach(task -> {
				try {
					task.execute(new TaskOutcome(task.toString()));
				} catch (Exception e) {
					log.error("Failed to execute shutdown task", e);
				}
			});
		}));

		// Invoke the main method
		try {
			main.getDeclaredMethod("main", String[].class).invoke(null, (Object) args);
		} catch (Exception e) {
			throw new RuntimeException("Failed to invoke main method in class: " + main.getName(), e);
		}

		// Execute tasks
		for (Task task : tasks) {
			if (task.initMetadata == null)
				throw new RuntimeException("Unregistered initialization task class");

			TaskOutcome outcome = new TaskOutcome(task.initMetadata.name());

			if (!task.initMetadata.condition().isEmpty() && !Config.getBoolean(task.initMetadata.condition())) {
				outcome.skipped = true;
			} else {
				try {
					task.outcome = task.execute(outcome);
				} catch (Exception e) {
					task.outcome = outcome.failure(e);
				}
			}

			if (!outcome.getOutcome().getResult() && task.initMetadata.fatal() && !outcome.isSkipped()) {
				log.error("A fatal error has occurred in task: {}", task.initMetadata.name());
				logTaskSummary();
				System.exit(1);
			}
		}

		// Print task summary if any task failed<commit_msg>Fix NPE with skipped task<commit_after>/******************************************************************************
 *                                                                            *
 *                    Copyright 2017 Subterranean Security                    *
 *                                                                            *
 *  Licensed under the Apache License, Version 2.0 (the "License");           *
 *  you may not use this file except in compliance with the License.          *
 *  You may obtain a copy of the License at                                   *
 *                                                                            *
 *      http://www.apache.org/licenses/LICENSE-2.0                            *
 *                                                                            *
 *  Unless required by applicable law or agreed to in writing, software       *
 *  distributed under the License is distributed on an "AS IS" BASIS,         *
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *
 *  See the License for the specific language governing permissions and       *
 *  limitations under the License.                                            *
 *                                                                            *
 *****************************************************************************/
package com.sandpolis.core.instance;

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sandpolis.core.instance.ConfigConstant.logging;
import com.sandpolis.core.instance.idle.IdleLoop;
import com.sandpolis.core.proto.util.Platform.Instance;
import com.sandpolis.core.proto.util.Platform.InstanceFlavor;
import com.sandpolis.core.proto.util.Result.Outcome;
import com.sandpolis.core.util.ProtoUtil;

/**
 * {@link MainDispatch} allows the instance's main method to configure
 * initialization tasks before they are sequentially executed by this class.
 * Idle tasks and shutdown tasks are also managed by this class.
 * 
 * @author cilki
 * @since 5.0.0
 */
public final class MainDispatch {

	public static final Logger log = LoggerFactory.getLogger(MainDispatch.class);

	/**
	 * A configurable list of tasks that initialize the instance.
	 */
	private static List<Task> tasks = new ArrayList<>();

	/**
	 * A configurable list of tasks that are executed on instance shutdown.
	 */
	private static List<Task> shutdown = new LinkedList<>();

	/**
	 * A {@link Thread} that runs idle tasks in the background.
	 */
	private static IdleLoop idle;

	/**
	 * The instance's main {@link Class}.
	 */
	private static Class<?> main = MainDispatch.class;

	/**
	 * The instance's {@link Instance} type.
	 */
	private static Instance instance;

	/**
	 * The instance's {@link InstanceFlavor} type.
	 */
	private static InstanceFlavor flavor;

	/**
	 * Get the {@link Class} that was dispatched.
	 * 
	 * @return The dispatched {@link Class} or {@code MainDispatch.class} if
	 *         {@link #dispatch} has not been called
	 */
	public static Class<?> getMain() {
		return main;
	}

	/**
	 * Get the {@link Instance} type.
	 * 
	 * @return The dispatched {@link Instance} or {@code null} if {@link #dispatch}
	 *         has not been called
	 */
	public static Instance getInstance() {
		return instance;
	}

	/**
	 * Get the {@link InstanceFlavor} type.
	 * 
	 * @return The dispatched {@link InstanceFlavor} or {@code null} if
	 *         {@link #dispatch} has not been called
	 */
	public static InstanceFlavor getInstanceFlavor() {
		return flavor;
	}

	/**
	 * Get the {@link IdleLoop}.
	 * 
	 * @return The registered {@link IdleLoop} or {@code null} if one has not been
	 *         registered
	 */
	public static IdleLoop getIdleLoop() {
		return idle;
	}

	/**
	 * Invokes the instance's main method (which should register initialization
	 * tasks with {@link MainDispatch}) and then initializes the instance.
	 * 
	 * @param main     The {@link Class} which contains the {@code main} to invoke
	 * @param args     The arguments to be passed to the {@code main}
	 * @param instance The instance's {@link Instance}
	 * @param flavor   The instance's {@link InstanceFlavor}
	 */
	public static void dispatch(Class<?> main, String[] args, Instance instance, InstanceFlavor flavor) {
		if (MainDispatch.main != MainDispatch.class)
			throw new IllegalStateException("Dispatch cannot be called more than once");

		MainDispatch.main = Objects.requireNonNull(main);
		MainDispatch.instance = Objects.requireNonNull(instance);
		MainDispatch.flavor = Objects.requireNonNull(flavor);

		long timestamp = System.currentTimeMillis();

		// Pass main arguments to the Config class
		Config.setArguments(args);

		// Setup exception handler
		Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
			log.error("An unexpected exception has occurred", throwable);
		});

		// Setup shutdown hook
		Runtime.getRuntime().addShutdownHook(new Thread(() -> {
			shutdown.forEach(task -> {
				try {
					task.execute(new TaskOutcome(task.toString()));
				} catch (Exception e) {
					log.error("Failed to execute shutdown task", e);
				}
			});
		}));

		// Invoke the main method
		try {
			main.getDeclaredMethod("main", String[].class).invoke(null, (Object) args);
		} catch (Exception e) {
			throw new RuntimeException("Failed to invoke main method in class: " + main.getName(), e);
		}

		// Execute tasks
		for (Task task : tasks) {
			if (task.initMetadata == null)
				throw new RuntimeException("Unregistered initialization task class");

			TaskOutcome outcome = new TaskOutcome(task.initMetadata.name());

			if (!task.initMetadata.condition().isEmpty() && !Config.getBoolean(task.initMetadata.condition())) {
				task.outcome = outcome.skipped();
			} else {
				try {
					task.outcome = task.execute(outcome);
				} catch (Exception e) {
					task.outcome = outcome.failure(e);
				}

				if (!outcome.getOutcome().getResult() && task.initMetadata.fatal()) {
					log.error("A fatal error has occurred in task: {}", task.initMetadata.name());
					logTaskSummary();
					System.exit(1);
				}
			}
		}

		// Print task summary if any task failed
{'fraction_non_alphanumeric': 0.12758647018401162, 'fraction_numerical': 0.002137894174238375, 'mean_word_length': 2.6062775330396475, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 36, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5664903', 'n_tokens_mistral': 3711, 'n_tokens_neox': 3325, 'n_words': 1092}