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

com.sshtools.jajafx.JajaApp Maven / Gradle / Ivy

The newest version!
package com.sshtools.jajafx;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Consumer;
import java.util.prefs.Preferences;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;

import com.install4j.api.launcher.StartupNotification;
import com.install4j.api.launcher.StartupNotification.Listener;
import com.sshtools.jaul.AppRegistry;
import com.sshtools.jaul.AppRegistry.App;
import com.sshtools.jaul.Phase;
import com.sshtools.jaul.UpdateableAppContext;

import picocli.CommandLine.Command;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Option;
import picocli.CommandLine.Spec;

@Command
public abstract class JajaApp, WND extends JajaFXAppWindow> implements Callable {

	public static abstract class JajaAppBuilder, BB extends JajaAppBuilder, BFXA extends JajaFXApp> {

		private Optional defaultPhase = Optional.empty();
		private Optional>> appClazz = Optional.empty();
		private Optional inceptionYear;
		private Optional appResources = Optional.empty();

		@SuppressWarnings("unchecked")
		public BB withAppResources(ResourceBundle appResources) {
			this.appResources = Optional.of(appResources);
			return (BB) this;
		}

		@SuppressWarnings("unchecked")
		public BB withApp(Class> appClazz) {
			this.appClazz = Optional.of(appClazz);
			return (BB) this;
		}

		public BB withDefaultPhase(Phase defaultPhase) {
			return withDefaultPhase(Optional.of(defaultPhase));
		}

		@SuppressWarnings("unchecked")
		public BB withDefaultPhase(Optional defaultPhase) {
			this.defaultPhase = defaultPhase;
			return (BB) this;
		}

		public BB withInceptionYear(int inceptionYear) {
			return withInceptionYear(Optional.of(inceptionYear));
		}

		@SuppressWarnings("unchecked")
		public BB withInceptionYear(Optional inceptionYear) {
			this.inceptionYear = inceptionYear;
			return (BB) this;
		}

		public abstract BA build();
	}
	
	static {
        SLF4JBridgeHandler.removeHandlersForRootLogger();
        SLF4JBridgeHandler.install();
	}

	static Logger LOG = LoggerFactory.getLogger(JajaApp.class);

	@Option(names = { "-W", "--standard-window-decorations" }, negatable = true, description = "Either prevent or force the use standard window decorations.")
	Optional standardWindowDecorations;

	@Deprecated
	@Option(names = { "--jaul-register" }, hidden = true, description = "Register this application with the JADAPTIVE update system and exit. Usually only called on installation.")
	boolean jaulRegister;

	@Deprecated
	@Option(names = { "--jaul-deregister" }, hidden = true, description = "De-register this application from the JADAPTIVE update system and exit. Usually only called on uninstallation.")
	boolean jaulDeregister;

	@Spec
	CommandSpec spec;

	private final Class> appClazz;
	private final Optional defaultPhase;
	private final Optional inceptionYear;
	private final ResourceBundle appResources;
	
	private AppUpdateService updateService;
	protected ScheduledExecutorService scheduler;
	private Optional app;
	private Optional onOpenRequest = Optional.empty();
	private FXA fxApp;

	private static JajaApp instance;

	protected JajaApp(JajaAppBuilder builder) {
		instance = this;
		this.appResources = builder.appResources
				.orElseThrow(() -> new IllegalStateException("App resources must be provided."));
		this.inceptionYear = builder.inceptionYear;
		this.appClazz = builder.appClazz.orElseThrow(() -> new IllegalStateException("App class must be provided"));
		this.defaultPhase = builder.defaultPhase;
		scheduler = Executors.newScheduledThreadPool(1);
	}

	@SuppressWarnings("unchecked")
	public void init(JajaFXApp fxApp) {
		this.fxApp = (FXA) fxApp;
	}

	public final FXA getFXApp() {
		return fxApp;
	}

	public final void setOnOpenRequest(Listener onOpenRequest) {
		this.onOpenRequest = Optional.ofNullable(onOpenRequest);
	}

	public final ScheduledExecutorService getScheduler() {
		return scheduler;
	}

	public final Preferences getAppPreferences() {
		return AppRegistry.getBestAppPreferences(app, this);
	}

	public final Preferences getUserPreferences() {
		return AppRegistry.getBestUserPreferences(app, this);
	}

	public static JajaApp getInstance() {
		return instance;
	}

	public void exit() {
		exit(0);
	}

	public void exit(int code) {
		scheduler.shutdown();
		System.exit(code);
	}

	public final Phase getDefaultPhaseForVersion() {
		return Phase.getDefaultPhaseForVersion(spec.version());
	}

	public UpdateableAppContext getUpdateContext() {
		return new UpdateableAppContext() {

			@Override
			public void setUpdatesDeferredUntil(long timeMs) {
				getAppPreferences().putLong(AppRegistry.KEY_DEFER, timeMs);
			}

			@Override
			public long getUpdatesDeferredUntil() {
				return getAppPreferences().getLong(AppRegistry.KEY_DEFER, 0);
			}

			@Override
			public Phase getPhase() {
				return Phase.valueOf(
						getAppPreferences().get(AppRegistry.KEY_PHASE, defaultPhase.orElse(getDefaultPhaseForVersion()).name()));
			}

			@Override
			public void setPhase(Phase phase) {
				getAppPreferences().put(AppRegistry.KEY_PHASE, phase.name());

			}

			@Override
			public boolean isAutomaticUpdates() {
				return getAppPreferences().getBoolean(AppRegistry.KEY_AUTOMATIC_UPDATES, true);
			}

			@Override
			public void setAutomaticUpdates(boolean automaticUpdates) {
				getAppPreferences().putBoolean(AppRegistry.KEY_AUTOMATIC_UPDATES, automaticUpdates);
			}

			@Override
			public ScheduledExecutorService getScheduler() {
				return scheduler;
			}

			@Override
			public String getVersion() {
				return spec.version()[0];
			}
		};
	}
	
	public final App getRegisteredApp() {
		return app.orElseThrow(() -> new IllegalStateException("No registered app."));
	}

	public final boolean isConsoleMode() {
		return false;
	}

	public final AppUpdateService getUpdateService() {
		if (updateService == null) {
			updateService = createUpdateService();
	        updateService.needsUpdatingProperty().addListener((c, o, n) -> getFXApp().needUpdate());
	        updateService.rescheduleCheck();
		}
		return updateService;
	}

	public final CommandSpec getCommandSpec() {
		return spec;
	}

	public final Integer call() throws Exception {
		initCall();
		if(jaulDeregister) {
			AppRegistry.get().deregister(getClass());
			return 0;
		}
		else if(jaulRegister) {
			AppRegistry.get().register(getClass());
			return 0;
		}
		else {
			app = locateApp();
			
			app.ifPresent(a -> onOpenRequest.ifPresent(o -> StartupNotification.registerStartupListener(o)));
			
			beforeCall();
			JajaFXApp.launch(appClazz, new String[0]);
			return 0;
		}
	}
	
	protected void setHandleQuit(boolean handleQuit) {
		StartupNotification.setHandleQuit(handleQuit);
	}

	protected void beforeCall() throws Exception {
	}

	protected void initCall() throws Exception {
	}

	protected final AppUpdateService createUpdateService() {
		try {
			if ("true".equals(System.getProperty("jajafx.dummyUpdates"))) {
				return new AppDummyUpdateService(this);
			}
			if(app.isPresent()) {
				return new AppInstall4JUpdateService(this, app.get());
			}
			else
				return createDefaultUpdateService();
						
		} catch (Throwable t) {
			if (LOG.isDebugEnabled())
				LOG.info("Failed to create Install4J update service, using dummy service.", t);
			else
				LOG.info("Failed to create Install4J update service, using dummy service. {}", t.getMessage());
			return createDefaultUpdateService();
		}
	}

	protected AppUpdateService createDefaultUpdateService() {
		return new AppNoUpdateService(this);
	}

	public final int getInceptionYear() {
		return inceptionYear.orElse(1900);
	}

	public final ResourceBundle getAppResources() {
		return appResources;
	}

	public final void update(Consumer onError) {
		getScheduler().execute(() -> {
			try {
				updateService.update();
			} catch (IOException ioe) {
				LOG.error("Failed to update.", ioe);
				onError.accept(ioe);
			}
		});
	}

	public final void updateCheck(Consumer onResult, Consumer onError) {
		getScheduler().execute(() -> {
			try {
				getUpdateService().checkForUpdate();
				onResult.accept(updateService.isNeedsUpdating());
			} catch (IOException ioe) {
				LOG.error("Failed to check for updates.", ioe);
				onError.accept(ioe);
			}
		});
	}

	private Optional locateApp() {
		try {
			return Optional.of(AppRegistry.get().launch(this.getClass()));
		}
		catch(Exception e) {
			LOG.warn(MessageFormat.format("Failed to determine app installation. No update features will be available, and application preferences root is now determined by the class name {0}. {1}", getClass().getName(), e.getMessage() == null ? "No message supplied." : e.getMessage()));
			return Optional.empty();
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy