View Javadoc
1   package de.spiritscorp.datasync;
2   /*
3   	Data Sync
4   		Application to synchronize your data
5   
6   	@author Tom Spirit
7   	@date 16.12.2021
8   	@version	1.1.0.0-alpha
9   	@email tomspirit@spiritscorp.network
10  
11  	Copyright ©
12  
13  This program is free software; you can redistribute it and/or modify
14  it under the terms of the GNU General Public License as published by
15  the Free Software Foundation; either version 3 of the License, or
16  (at your option) any later version.
17  
18  This program is distributed in the hope that it will be useful,
19  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  GNU General Public License for more details.
22  
23  You should have received a copy of the GNU General Public License
24  along with this program. If not, see <http://www.gnu.org/licenses/>.
25  */
26  
27  import java.nio.file.Path;
28  import java.time.LocalDateTime;
29  import java.time.ZoneId;
30  import java.time.format.DateTimeFormatter;
31  import java.time.format.FormatStyle;
32  import java.util.Locale;
33  
34  import de.spiritscorp.datasync.gui.Gui;
35  import de.spiritscorp.datasync.io.Debug;
36  import de.spiritscorp.datasync.io.PreferenceManager;
37  import javafx.application.Application;
38  
39  /**
40   * Main application entry point responsible for runtime arguments parsing,
41   * debug subsystems orchestration, and boots-strapping the JavaFX platform lifecycle.
42   *
43   * @author Tom Spirit
44   */
45  @SuppressWarnings( { "PMD.LongVariable" } )
46  public final class Main { // NOPMD ShortClassName
47  
48  	/**
49  	 * The current version of the application in semantic format,
50  	 * including the development stage (e.g., alpha).
51  	 */
52  	public static final String VERSION = "V 1.1.0.0-alpha";
53  
54  	/**
55  	 * The timeout limit in milliseconds for regular, worker threads
56  	 * before a forced termination is triggered.
57  	 */
58  	public static final int EXIT_THREAD_TIMEOUT = 10_000;
59  
60  	/**
61  	 * The timeout limit in milliseconds for asynchronous background processes
62  	 * (e.g., automated file or task scans).
63  	 */
64  	public static final int BACKGROUND_THREAD_TIMEOUT = 20_000;
65  
66  	/**
67  	 * The delay time in seconds used for displaying or fading out
68  	 * status and informational messages within the GUI.
69  	 */
70  	public static final int INFO_DELAY = 4;
71  
72  	/** Config folder flag long */
73  	public static final String CONFIG_DIR_LONG = "--config-dir";
74  	/** Config folder flag short */
75  	public static final String CONFIG_DIR_SHORT = "-c";
76  	/** Boot delay flag long */
77  	public static final String BOOT_DELAY_LONG = "--boot-delay";
78  	/** Boot Delay flag short */
79  	public static final String BOOT_DELAY_SHORT = "-b";
80  	/** Debug flag long */
81  	public static final String DEBUG_LONG = "--debug";
82  	/** Debug flag short */
83  	public static final String DEBUG_SHORT = "-d";
84  	/** Debug to file flag long */
85  	public static final String DEBUG_TO_FILE_LONG = "--debug-to-file";
86  	/** Debug to file flag short */
87  	public static final String DEBUG_TO_FILE_SHORT = "-f";
88  
89  	/** Debug mode */
90  	private static boolean debug;
91  	/** Boot delay mode */
92  	private static boolean firstStart;
93  	/** Helper to jump over the next argument if config dir needs 2 */
94  	private static boolean folderJumpArg;
95  	/** Will be set debug to file */
96  	private static boolean toFile;
97  
98  	/**
99  	 * Application entry point. Orchestrates the initial boot sequence by parsing
100 	 * command-line options and bootstrapping the underlying JavaFX application subsystem.
101 	 * <p>
102 	 * This method delegates argument parsing to {@link #parseArguments(String[])} before
103 	 * handing over control to the JavaFX application lifecycle via {@link Application#launch(Class, String...)}.
104 	 * </p>
105 	 *
106 	 * @param args Runtime command-line execution flags and configuration parameters.
107 	 */
108 	public static void main( final String[] args ) {
109 		parseArguments( args );
110 		Application.launch( Gui.class, args );
111 	}
112 
113 	/**
114 	 * Checks whether the application was launched automatically by the operating system's
115 	 * startup/autostart routine.
116 	 * <p>
117 	 * When {@code true}, a timer delay is initialized to reduce system resource contention
118 	 * during OS boot, and the application is instructed to start minimized in the background.
119 	 * This flag is managed and set automatically during the autostart registration process.
120 	 *
121 	 * @return {@code true} if the application was triggered via OS autostart;
122 	 *         {@code false} if it was started manually by the user.
123 	 */
124 	public static boolean isFirstStart() { return firstStart; }
125 
126 	/**
127 	 * Checks whether the debug mode is active for extended verbose and additional runtime diagnostic outputs.
128 	 *
129 	 * @return {@code true} if advanced diagnostic information should be emitted;
130 	 *         {@code false} otherwise.
131 	 */
132 	public static boolean isDebug() { return debug; }
133 
134 	/**
135 	 * Checks whether the debug mode is active for extended verbose and additional runtime diagnostic outputs.
136 	 *
137 	 * @return {@code true} if advanced diagnostic information should be emitted;
138 	 *         {@code false} otherwise.
139 	 */
140 	public static boolean isDebugToFile() { return toFile; }
141 
142 	/**
143 	 * Evaluates and processes runtime command-line arguments in a single pass to configure
144 	 * global application states and subsystem parameters.
145 	 * <p>
146 	 * The parser evaluates standard flags for debugging, diagnostic routing, execution delays,
147 	 * and configuration root directory adjustments. For key-value configurations, it supports
148 	 * both standard inline assignment (e.g., {@code --config-dir=/path}) and safe whitespace
149 	 * lookahead token isolation (e.g., {@code -c /path}), ensuring subsequent flags are not
150 	 * accidentally consumed as paths.
151 	 * </p>
152 	 *
153 	 * @param args An array of string arguments passed to the application upon startup.
154 	 *             Null elements within the array are safely ignored.
155 	 */
156 	/*package*/ static void parseArguments( final String... args ) {
157 
158 		final PreferenceManager manager = PreferenceManager.getInstance();
159 
160 		// Single-pass argument processing to minimize iteration overhead
161 		for( int i = 0; i < args.length; i++ ) {
162 			if( args[i] == null || folderJumpArg ) {
163 				folderJumpArg = false;
164 				continue;
165 			}
166 			evaluateArgumentFlags( args, i, manager );
167 		}
168 		if( toFile ) Debug.setDebugToFile();
169 		// Initialize debug diagnostics if debog is enabled
170 		if( debug ) initializeDebugDiagnostics( manager.getConfigPath() );
171 	}
172 
173 	/**
174 	 * Resets the global execution states and diagnostic tracking flags to their
175 	 * initial default values.
176 	 * <p>
177 	 * This helper method is intended exclusively for test isolation purposes (e.g., within
178 	 * {@code @BeforeEach} setup methods) to clear out internal static modifications
179 	 * between consecutive test executions and guarantee a deterministic environment.
180 	 * </p>
181 	 */
182 	/*package*/ static void resetForTesting() {
183 		debug = false;
184 		firstStart = false;
185 	}
186 
187 	/**
188 	 * Evaluates individual argument flags and updates global execution variables.
189 	 */
190 	private static void evaluateArgumentFlags( final String[] args, final int currentIndex, final PreferenceManager manager ) {
191 		final String arg = args[currentIndex].trim();
192 		final String generalArg = arg.toLowerCase( Locale.ROOT );
193 		switch( generalArg ) {
194 			// Modern multi-labels: replaces the logical OR (||) chains
195 			case BOOT_DELAY_LONG, BOOT_DELAY_SHORT ->
196 				firstStart = true;
197 
198 			case DEBUG_LONG, DEBUG_SHORT ->
199 				debug = true;
200 
201 			case DEBUG_TO_FILE_LONG, DEBUG_TO_FILE_SHORT -> {
202 				toFile = true;
203 				debug = true;
204 			}
205 
206 			// Pattern Matching with a 'when' guard to handle the .startsWith() logic
207 			case final String genArg when genArg.startsWith( CONFIG_DIR_LONG ) || genArg.startsWith( CONFIG_DIR_SHORT ) ->
208 				handleConfigDirectoryArgument( args, currentIndex, manager );
209 
210 			default -> {
211 				// No match found, skip silently
212 			}
213 		}
214 	}
215 
216 	/**
217 	 * Handles the logic for extracting and setting the configuration directory path.
218 	 *
219 	 * @param args         The command-line arguments.
220 	 * @param currentIndex The current index in the arguments array.
221 	 * @param manager      The preference manager instance.
222 	 */
223 	private static void handleConfigDirectoryArgument( final String[] args, final int currentIndex, final PreferenceManager manager ) {
224 		final String arg = args[currentIndex].trim();
225 		final String generalArg = arg.toLowerCase( Locale.ROOT );
226 		String configFolder = "";
227 		if( generalArg.contains( "=" ) ) {
228 			configFolder = arg.substring( arg.indexOf( '=' ) + 1 );
229 		}else if( currentIndex + 1 < args.length && !args[currentIndex + 1].startsWith( "-" ) ) {
230 			// Safeguard: Only consume next argument if it's not another flag
231 			configFolder = args[currentIndex + 1].trim();
232 			folderJumpArg = true;
233 		}
234 		if( !configFolder.isBlank() ) {
235 			manager.initGlobalRootConfigPath( Path.of( configFolder ) );
236 		}
237 	}
238 
239 	/**
240 	 * Initializes debug diagnostics with application information.
241 	 *
242 	 * @param manager The preference manager instance.
243 	 */
244 	private static void initializeDebugDiagnostics( final Path configPath ) {
245 		Debug.printDebugTimeless( "%nDEBUG BEGIN -> [%s]: %s",
246 				System.getProperty( "app.instance.name", "Standard Instance" ),
247 				LocalDateTime.now( ZoneId.systemDefault() ).format( DateTimeFormatter.ofLocalizedDateTime( FormatStyle.FULL, FormatStyle.SHORT ) ) );
248 		Debug.printDebug( "[Info] Data Sync Application initialized. Beginning system initialization." );
249 		Debug.printDebug( "[Setup] Set config root path -> %s", configPath.toString() );
250 	}
251 
252 }