package org.netbeans.gradle.project.properties; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingUtilities; import org.jtrim.cancel.Cancellation; import org.jtrim.cancel.CancellationToken; import org.jtrim.collections.CollectionsEx; import org.jtrim.concurrent.CancelableTask; import org.jtrim.concurrent.GenericUpdateTaskExecutor; import org.jtrim.concurrent.MonitorableTaskExecutor; import org.jtrim.concurrent.TaskExecutor; import org.jtrim.concurrent.UpdateTaskExecutor; import org.jtrim.event.ListenerRef; import org.jtrim.property.MutableProperty; import org.jtrim.property.PropertySource; import org.jtrim.utils.ExceptionHelper; import org.netbeans.gradle.project.api.config.ProfileDef; import org.netbeans.gradle.project.api.config.ProfileKey; import org.netbeans.gradle.project.event.ChangeListenerManager; import org.netbeans.gradle.project.event.GenericChangeListenerManager; import org.netbeans.gradle.project.util.NbTaskExecutors; import org.netbeans.gradle.project.util.SerializationUtils2; public final class NbGradleConfigProvider { private static final Logger LOGGER = Logger.getLogger(NbGradleConfigProvider.class.getName()); // Must be FIFO executor private static final TaskExecutor PROFILE_APPLIER_EXECUTOR = NbTaskExecutors.newExecutor("Profile-applier", 1); private static final String LAST_PROFILE_FILE = "last-profile"; private static final Lock CONFIG_PROVIDERS_LOCK = new ReentrantLock(); private static final Map<Path, NbGradleConfigProvider> CONFIG_PROVIDERS = new WeakValueHashMap<>(); private final Path rootDirectory; private final ChangeListenerManager activeConfigChangeListeners; private final ChangeListenerManager configsChangeListeners; private final AtomicReference<List<NbGradleConfiguration>> configs; private final AtomicReference<NbGradleConfiguration> activeConfigRef; private final MultiProfileProperties multiProfileProperties; private final ProfileSettingsContainer settingsContainer; private final UpdateTaskExecutor profileApplierExecutor; private final MonitorableTaskExecutor profileIOExecutor; private final MutableProperty<NbGradleConfiguration> activeConfiguration; private final PropertySource<Collection<NbGradleConfiguration>> configurations; private NbGradleConfigProvider( Path rootDirectory, NbGradleConfiguration selectedConfig, List<NbGradleConfiguration> initialConfigs, MultiProfileProperties multiProfileProperties, ProfileSettingsContainer settingsContainer) { ExceptionHelper.checkNotNullArgument(rootDirectory, "rootDirectory"); ExceptionHelper.checkNotNullArgument(multiProfileProperties, "multiProfileProperties"); ExceptionHelper.checkNotNullArgument(settingsContainer, "settingsContainer"); this.rootDirectory = rootDirectory; this.activeConfigChangeListeners = GenericChangeListenerManager.getSwingNotifier(); this.configsChangeListeners = GenericChangeListenerManager.getSwingNotifier(); this.activeConfigRef = new AtomicReference<>(selectedConfig); this.configs = new AtomicReference<>(CollectionsEx.readOnlyCopy(initialConfigs)); this.multiProfileProperties = multiProfileProperties; this.settingsContainer = settingsContainer; this.profileApplierExecutor = new GenericUpdateTaskExecutor(PROFILE_APPLIER_EXECUTOR); this.profileIOExecutor = NbTaskExecutors.newDefaultFifoExecutor(); this.configurations = NbProperties.<Collection<NbGradleConfiguration>>atomicValueView(configs, configsChangeListeners); this.activeConfiguration = new MutableProperty<NbGradleConfiguration>() { @Override public void setValue(NbGradleConfiguration config) { setActiveConfiguration(config); } @Override public NbGradleConfiguration getValue() { return getActiveConfiguration(); } @Override public ListenerRef addChangeListener(Runnable listener) { return activeConfigChangeListeners.registerListener(listener); } }; } private static NbGradleConfigProvider tryGetConfigProvider(Path rootDir) { CONFIG_PROVIDERS_LOCK.lock(); try { return CONFIG_PROVIDERS.get(rootDir); } finally { CONFIG_PROVIDERS_LOCK.unlock(); } } public static NbGradleConfigProvider getConfigProvider(Path rootDir) { NbGradleConfigProvider result = tryGetConfigProvider(rootDir); if (result != null) { return result; } // This path is usually only taken on the first load of the config. // There is a chance that it might get loaded again in some rare // cases but then we will detect it later and discard the config // reading done the second time. List<NbGradleConfiguration> availableConfigs = readAvailableConfigs(rootDir); NbGradleConfiguration initialConfig = readLastSelectedProfile(rootDir, availableConfigs); ProfileSettingsContainer settingsContainer = ProfileSettingsContainer.getDefault(); List<SingleProfileSettingsEx> initialProfiles = getLoadedProfileSettings(rootDir, settingsContainer, initialConfig.getProfileKey()); MultiProfileProperties profileProperties = new MultiProfileProperties(initialProfiles); result = new NbGradleConfigProvider(rootDir, initialConfig, availableConfigs, profileProperties, settingsContainer); CONFIG_PROVIDERS_LOCK.lock(); try { NbGradleConfigProvider currentProvider = CONFIG_PROVIDERS.get(rootDir); if (currentProvider == null) { CONFIG_PROVIDERS.put(rootDir, result); } else { result = currentProvider; } } finally { CONFIG_PROVIDERS_LOCK.unlock(); } return result; } public Path getRootDirectory() { return rootDirectory; } public ActiveSettingsQueryEx getActiveSettingsQuery() { return multiProfileProperties; } public ProfileSettingsContainer getProfileSettingsContainer() { return settingsContainer; } private void removeFromConfig(NbGradleConfiguration config) { List<NbGradleConfiguration> currentList; List<NbGradleConfiguration> newList; do { currentList = configs.get(); newList = new ArrayList<>(currentList); newList.remove(config); newList = Collections.unmodifiableList(newList); } while (!configs.compareAndSet(currentList, newList)); } private void addToConfig(Collection<NbGradleConfiguration> toAdd) { List<NbGradleConfiguration> currentList; List<NbGradleConfiguration> newList; do { currentList = configs.get(); Set<NbGradleConfiguration> configSet = new HashSet<>(currentList); configSet.addAll(toAdd); newList = new ArrayList<>(configSet); NbGradleConfiguration.sortProfiles(newList); newList = Collections.unmodifiableList(newList); } while (!configs.compareAndSet(currentList, newList)); } public void removeConfiguration(final NbGradleConfiguration config) { if (NbGradleConfiguration.DEFAULT_CONFIG.equals(config)) { LOGGER.warning("Cannot remove the default configuration"); return; } profileIOExecutor.execute(Cancellation.UNCANCELABLE_TOKEN, new CancelableTask() { @Override public void execute(CancellationToken cancelToken) throws IOException { removeFromConfig(config); Path profileFile = SettingsFiles.getFilesForProfile(rootDirectory, config.getProfileDef())[0]; if (!Files.deleteIfExists(profileFile)) { LOGGER.log(Level.INFO, "Profile was deleted but no profile file was found: {0}", profileFile); } fireConfigurationListChange(); } }, null); } public void addConfiguration(final NbGradleConfiguration config) { executeOnEdt(new Runnable() { @Override public void run() { addToConfig(Collections.singleton(config)); fireConfigurationListChange(); } }); } private void executeOnEdt(Runnable task) { assert task != null; if (SwingUtilities.isEventDispatchThread()) { task.run(); } else { SwingUtilities.invokeLater(task); } } private static List<NbGradleConfiguration> readAvailableConfigs(Path rootDir) { Collection<ProfileDef> profileDefs = SettingsFiles.getAvailableProfiles(rootDir); List<NbGradleConfiguration> result = new ArrayList<>(profileDefs.size() + 1); result.add(NbGradleConfiguration.DEFAULT_CONFIG); for (ProfileDef profileDef: profileDefs) { result.add(new NbGradleConfiguration(profileDef)); } return result; } private Path getLastProfileFile() { return getLastProfileFile(rootDirectory); } private static Path getLastProfileFile(Path rootDirectory) { return SettingsFiles.getPrivateSettingsDir(rootDirectory).resolve(LAST_PROFILE_FILE); } private static NbGradleConfiguration readLastSelectedProfile( Path rootDirectory, List<NbGradleConfiguration> availableConfigs) { NbGradleConfiguration result = tryReadLastSelectedProfile(rootDirectory, availableConfigs); return result != null ? result : NbGradleConfiguration.DEFAULT_CONFIG; } private static NbGradleConfiguration tryReadLastSelectedProfile( Path rootDirectory, List<NbGradleConfiguration> availableConfigs) { Path lastProfileFile = getLastProfileFile(rootDirectory); if (!Files.isRegularFile(lastProfileFile)) { return null; } SavedProfileDef savedDef; try { savedDef = (SavedProfileDef)SerializationUtils2.deserializeFile(lastProfileFile); } catch (Exception ex) { LOGGER.log(Level.WARNING, "Failed to read last profile.", ex); return null; } return savedDef.findSameConfig(availableConfigs); } private void saveActiveProfileNow() throws IOException { assert profileIOExecutor.isExecutingInThis(); Path lastProfileFile = getLastProfileFile(); NbGradleConfiguration config = activeConfigRef.get(); if (config != null) { ProfileDef profileDef = config.getProfileDef(); if (profileDef == null) { if (!Files.deleteIfExists(lastProfileFile)) { LOGGER.log(Level.FINE, "Last profile file could not be deleted: {0}", lastProfileFile); } return; } SavedProfileDef savedDef = new SavedProfileDef(profileDef); try { Path parentFile = lastProfileFile.getParent(); if (parentFile != null) { Files.createDirectories(parentFile); } SerializationUtils2.serializeToFile(lastProfileFile, savedDef); } catch (IOException ex) { LOGGER.log(Level.WARNING, "Failed to read last profile.", ex); } } } private void saveActiveProfile() { profileIOExecutor.execute(Cancellation.UNCANCELABLE_TOKEN, new CancelableTask() { @Override public void execute(CancellationToken cancelToken) throws IOException { saveActiveProfileNow(); } }, null); } private void fireActiveConfigurationListChange() { activeConfigChangeListeners.fireEventually(); } private void fireConfigurationListChange() { configsChangeListeners.fireEventually(); } public Collection<NbGradleConfiguration> getConfigurations() { return configs.get(); } public NbGradleConfiguration getActiveConfiguration() { return activeConfigRef.get(); } private static List<SingleProfileSettingsEx> getLoadedProfileSettings( Path rootDirectory, ProfileSettingsContainer settingsContainer, ProfileKey profileKey) { ProfileSettingsKey key = ProjectProfileSettingsKey.getForProject(rootDirectory, profileKey); List<SingleProfileSettingsEx> profileSettings = settingsContainer.loadAllProfileSettings(key.getWithFallbacks()); return profileSettings; } private List<SingleProfileSettingsEx> getLoadedProfileSettings(ProfileKey profileKey) { return getLoadedProfileSettings(rootDirectory, settingsContainer, profileKey); } private void updateByKeyNow(ProfileKey profileKey) { multiProfileProperties.setProfileSettings(getLoadedProfileSettings(profileKey)); } private void updateByKey(final ProfileKey profileKey) { profileApplierExecutor.execute(new Runnable() { @Override public void run() { updateByKeyNow(profileKey); } }); } public void setActiveConfiguration(final NbGradleConfiguration configuration) { if (configuration == null) { LOGGER.warning("Attempting to set null configuration."); return; } final NbGradleConfiguration prevConfig = activeConfigRef.getAndSet(configuration); if (!prevConfig.equals(configuration)) { updateByKey(configuration.getProfileKey()); fireActiveConfigurationListChange(); } saveActiveProfile(); } public boolean configurationsAffectAction(String command) { return true; } public PropertySource<Collection<NbGradleConfiguration>> configurations() { return configurations; } public MutableProperty<NbGradleConfiguration> activeConfiguration() { return activeConfiguration; } public ListenerRef addActiveConfigChangeListener(Runnable listener) { return activeConfigChangeListeners.registerListener(listener); } }