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 }