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