001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.cli.processor.command;
007
008import java.util.List;
009import java.util.ServiceLoader;
010import java.util.ServiceLoader.Provider;
011import java.util.stream.Collectors;
012
013import dev.metaschema.core.util.ObjectUtils;
014import edu.umd.cs.findbugs.annotations.NonNull;
015import nl.talsmasoftware.lazy4j.Lazy;
016
017/**
018 * A service that loads commands using SPI.
019 * <p>
020 * This class implements the singleton pattern to ensure a single instance of
021 * the command service is used throughout the application.
022 *
023 * @see ServiceLoader for more information
024 */
025public final class CommandService {
026  private static final Lazy<CommandService> INSTANCE = Lazy.of(CommandService::new);
027  @NonNull
028  private final Lazy<List<ICommand>> cachedCommands;
029
030  /**
031   * Get the singleton instance of the function service.
032   *
033   * @return the service instance
034   */
035  public static CommandService getInstance() {
036    return INSTANCE.get();
037  }
038
039  /**
040   * Construct a new service.
041   * <p>
042   * Initializes the ServiceLoader for ICommand implementations and caches the
043   * loaded commands for thread-safe access.
044   * <p>
045   * This constructor is private to enforce the singleton pattern.
046   */
047  private CommandService() {
048    this.cachedCommands = Lazy.of(() -> {
049      ServiceLoader<ICommand> loader = ServiceLoader.load(ICommand.class);
050      return ObjectUtils.notNull(loader.stream()
051          .map(Provider<ICommand>::get)
052          .collect(Collectors.toUnmodifiableList()));
053    });
054  }
055
056  /**
057   * Get the loaded commands.
058   * <p>
059   * Commands are loaded once on first access and cached for subsequent calls.
060   * This ensures thread-safe access and consistent results across calls.
061   *
062   * @return the list of loaded commands
063   */
064  @NonNull
065  public List<ICommand> getCommands() {
066    return cachedCommands.get();
067  }
068}