001/*
002 * Copyright 2019-2021 M. Sean Gilligan.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package app.supernaut.fx;
017
018import app.supernaut.BackgroundApp;
019import javafx.application.Application;
020
021import java.util.NoSuchElementException;
022import java.util.Optional;
023import java.util.ServiceLoader;
024import java.util.concurrent.CompletableFuture;
025import java.util.function.Predicate;
026
027/**
028 * Launcher for Supernaut.FX (JavaFX) applications. By using this launcher, your applications
029 * can <i>implement</i> the {@link ApplicationDelegate} interface instead of <i>extending</i>
030 * {@link Application} and have their constructor dependency injected -- see {@link ApplicationDelegate} for
031 * an explanation of the advantages and details of this approach.
032 */
033public interface FxLauncher {
034    /**
035     * Launch and run the application on the current thread.
036     * Does not return until after ApplicationDelegate closes.
037     * @param args command-line args
038     * @param appDelegate class object for ApplicationDelegate
039     * @param backgroundApp class object for BackgroundApp
040     */
041    void launch(String[] args, Class<? extends ApplicationDelegate> appDelegate, Class<? extends BackgroundApp> backgroundApp);
042
043    /**
044     * Launch and run the application on the current thread. Uses default/no-op background application.
045     * Does not return until after ApplicationDelegate closes.
046     * @param args command-line args
047     * @param appDelegate class object for ApplicationDelegate
048     */
049    void launch(String[] args, Class<? extends ApplicationDelegate> appDelegate);
050
051    /**
052     * Launch and run the application on a newly created thread.
053     * This method is useful for testing and possibly for other
054     * application startup scenarios.
055     *
056     * @param args command-line args
057     * @param appDelegate class object for ApplicationDelegate
058     * @param backgroundApp class object for BackgroundApp
059     * @return A future that is completed when ApplicationDelegate app is initialized
060     */
061    CompletableFuture<ApplicationDelegate> launchAsync(String[] args, Class<? extends ApplicationDelegate> appDelegate, Class<? extends BackgroundApp> backgroundApp);
062
063    /**
064     * Get a future that will be completed when the ApplicationDelegate
065     * is initialized.
066     *
067     * @return A future that is completed when ApplicationDelegate is initialized
068     */
069    CompletableFuture<ApplicationDelegate> getAppDelegate();
070
071    /**
072     * Get a future that will be completed when the Background app
073     * is initialized.
074     *
075     * @return A future that is completed when Background app is initialized
076     */
077    CompletableFuture<BackgroundApp> getBackgroundApp();
078
079    /**
080     * Construct a {@link ApplicationDelegate} that is a delegate to {@code OpenJfxProxyApplication}.
081     * @param jfxApplication The OpenJfx "proxy" app instance
082     * @return A newly constructed (and possibly injected) ApplicationDelegate
083     */
084    ApplicationDelegate createAppDelegate(Application jfxApplication);
085
086    /**
087     * Implementations must implement this method to return a unique name
088     * @return A unique name for this DI-capable {@link FxLauncher} implementation
089     */
090    String name();
091
092    /**
093     * Find a FxLauncher provider by name
094     *
095     * @param name Name (e.g. "micronaut")
096     * @return an FxLaunder instance
097     * @throws NoSuchElementException if not found
098     */
099    static FxLauncher byName(String name) {
100        return findFirst(launcher -> launcher.name().equals(name))
101                .orElseThrow(() -> new NoSuchElementException("Launcher " + name + " not found."));
102    }
103
104    /**
105     * Find default FxLauncher
106     *
107     * @return an FxLaunder instance
108     * @throws NoSuchElementException if not found
109     */
110    static FxLauncher find() {
111        return findFirst(FxLauncher::defaultFilter)
112                .orElseThrow(() -> new NoSuchElementException("Default Launcher not found."));
113    }
114
115    /**
116     * Find a launcher using a custom predicate
117     * @param filter predicate for finding a launcher
118     * @return the <b>first</b> launcher matching the predicate, if any
119     */
120    static Optional<FxLauncher> findFirst(Predicate<FxLauncher> filter) {
121        ServiceLoader<FxLauncher> loader = ServiceLoader.load(FxLauncher.class);
122        return loader.stream()
123                .map(ServiceLoader.Provider::get)
124                .filter(FxLauncher::defaultFilter)
125                .findFirst();
126    }
127
128    /**
129     * Find the first available launcher that isn't the {@link app.supernaut.fx.sample.SimpleFxLauncher}
130     * @param launcher a candidate launcher
131     * @return true if it should be "found"
132     */
133    private static boolean defaultFilter(FxLauncher launcher) {
134        return !launcher.name().equals("simple");
135    }
136}