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}