| /*=============================================================================# |
| # Copyright (c) 2007, 2021 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.internal.r.core.renv; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.locks.Lock; |
| import java.util.concurrent.locks.ReadWriteLock; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| |
| import org.osgi.service.prefs.BackingStoreException; |
| import org.osgi.service.prefs.Preferences; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences; |
| import org.eclipse.core.runtime.preferences.IScopeContext; |
| import org.eclipse.core.runtime.preferences.InstanceScope; |
| |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.collections.ImList; |
| import org.eclipse.statet.jcommons.lang.NonNull; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| |
| import org.eclipse.statet.ecommons.preferences.core.Preference; |
| import org.eclipse.statet.ecommons.preferences.core.Preference.IntPref; |
| import org.eclipse.statet.ecommons.preferences.core.Preference.LongPref; |
| import org.eclipse.statet.ecommons.preferences.core.Preference.StringPref; |
| import org.eclipse.statet.ecommons.preferences.core.PreferenceAccess; |
| import org.eclipse.statet.ecommons.preferences.core.PreferenceSetService; |
| import org.eclipse.statet.ecommons.preferences.core.util.PreferenceUtils; |
| |
| import org.eclipse.statet.internal.r.core.Messages; |
| import org.eclipse.statet.internal.r.core.RCorePlugin; |
| import org.eclipse.statet.r.core.RCore; |
| import org.eclipse.statet.r.core.renv.IREnvConfiguration; |
| import org.eclipse.statet.r.core.renv.IREnvConfiguration.WorkingCopy; |
| import org.eclipse.statet.r.core.renv.IREnvManager; |
| import org.eclipse.statet.rj.renv.core.REnv; |
| import org.eclipse.statet.rj.renv.core.REnvConfiguration; |
| import org.eclipse.statet.rj.rsetups.RSetup; |
| import org.eclipse.statet.rj.rsetups.RSetupUtil; |
| |
| |
| /** |
| * Implementation of IREnvManager |
| */ |
| public class REnvManagerImpl implements IREnvManager { |
| |
| |
| private static final String USER_ENV_PREFIX= "user-"; //$NON-NLS-1$ |
| private static final String USER_LOCAL_ID_PREFIX= IREnvConfiguration.USER_LOCAL_TYPE + '-'; |
| private static final String USER_REMOTE_ID_PREFIX= IREnvConfiguration.USER_REMOTE_TYPE + '-'; |
| private static final String CONTRIB_TEMP_REMOTE_ENV_ID_PREFIX= IREnvConfiguration.CONTRIB_TEMP_REMOTE_TYPE + '-'; |
| |
| |
| private static final StringPref PREF_DEFAULT_CONFIGURATION_NAME= new StringPref( |
| IREnvManager.PREF_QUALIFIER, "default_configuration.name"); //$NON-NLS-1$ |
| |
| private static final IntPref PREF_VERSION= new IntPref( |
| IREnvManager.PREF_QUALIFIER, "version"); //$NON-NLS-1$ |
| |
| private static final LongPref STAMP_PREF= new LongPref( |
| IREnvManager.PREF_QUALIFIER, "stamp"); //$NON-NLS-1$ |
| |
| private static @Nullable REnv getActualREnv(final REnv rEnv) { |
| final IREnvConfiguration config= rEnv.get(IREnvConfiguration.class); |
| return (config != null) ? config.getREnv() : null; |
| } |
| |
| |
| private volatile int state; |
| private final ReadWriteLock lock; |
| |
| private Map<String, REnvConfigurationImpl> idMap; |
| private final AliasREnv defaultEnv= new AliasREnv(RCore.DEFAULT_WORKBENCH_ENV_ID); |
| |
| |
| public REnvManagerImpl() { |
| this.state= 0; |
| this.lock= new ReentrantReadWriteLock(true); |
| } |
| |
| @Override |
| public Lock getReadLock() { |
| return this.lock.readLock(); |
| } |
| |
| public void dispose() { |
| this.lock.writeLock().lock(); |
| try { |
| this.state= 2; |
| if (this.idMap != null) { |
| this.idMap.clear(); |
| this.idMap= null; |
| } |
| } |
| finally { |
| this.lock.writeLock().unlock(); |
| } |
| } |
| |
| private void checkAndLock(final boolean writeLock) { |
| // check, if lazy loading is required |
| if (this.state < 1) { |
| synchronized (this) { |
| try { |
| if (this.state < 1) { |
| load(); |
| this.state= 1; |
| } |
| } |
| catch (final BackingStoreException e) { |
| this.state= 101; |
| RCorePlugin.logError(Messages.REnvManager_error_Accessing_message, e); |
| } |
| } |
| } |
| |
| // lock and check ready |
| if (writeLock) { |
| this.lock.writeLock().lock(); |
| } |
| else { |
| this.lock.readLock().lock(); |
| } |
| if (this.state > 1) { |
| throw new IllegalStateException(Messages.REnvManager_error_Dispose_message); |
| } |
| } |
| |
| private boolean update(final ImList<IREnvConfiguration> configs, final String defaultREnvId) { |
| final Set<String> newREnvs= new HashSet<>(); |
| for (final IREnvConfiguration config : configs) { |
| newREnvs.add(config.getREnv().getId()); |
| } |
| final Set<String> oldIds= new HashSet<>(this.idMap.keySet()); |
| |
| // update or add configurations |
| boolean changed= false; |
| for (final IREnvConfiguration config : configs) { |
| changed= doAdd(config, changed); |
| final REnv rEnv= config.getREnv(); |
| oldIds.remove(rEnv.getId()); |
| } |
| // remove old configurations |
| for (final String id : oldIds) { |
| changed= doDelete(id, changed); |
| } |
| |
| changed= doSetDefault(defaultREnvId, changed); |
| |
| // dirty? |
| return changed; |
| } |
| |
| private boolean doAdd(final IREnvConfiguration config, boolean changed) { |
| final REnvConfigurationImpl newConfig= new REnvConfigurationImpl(config); |
| final ActualREnv rEnv= (ActualREnv) newConfig.getREnv(); |
| final IREnvConfiguration oldConfig= this.idMap.put(rEnv.getId(), newConfig); |
| |
| rEnv.name= newConfig.getName(); |
| rEnv.config= newConfig; |
| |
| if (!changed && !Objects.equals(oldConfig, newConfig)) { |
| changed= true; |
| } |
| return changed; |
| } |
| |
| private boolean doDelete(final String id, boolean changed) { |
| final REnvConfigurationImpl rEnvConfig= this.idMap.remove(id); |
| if (rEnvConfig != null) { |
| final ActualREnv rEnv= (ActualREnv) rEnvConfig.getREnv(); |
| rEnv.isDeleted= true; |
| // rEnv.config= null; |
| changed |= true; |
| } |
| |
| return changed; |
| } |
| |
| private boolean doSetDefault(final String id, boolean changed) { |
| final REnv oldDefault= getActualREnv(this.defaultEnv); |
| updateDefault(id); |
| final REnv newDefault= getActualREnv(this.defaultEnv); |
| |
| if (!changed && !Objects.equals(oldDefault, newDefault)) { |
| changed= true; |
| } |
| return changed; |
| } |
| |
| private boolean doRefreshDefault(boolean changed) { |
| final REnv oldDefault= getActualREnv(this.defaultEnv); |
| updateDefault((oldDefault != null) ? oldDefault.getId() : null); |
| final REnv newDefault= getActualREnv(this.defaultEnv); |
| |
| if (!changed && !Objects.equals(oldDefault, newDefault)) { |
| changed= true; |
| } |
| return changed; |
| } |
| |
| |
| private void updateDefault(final String defaultConfigId) { |
| IREnvConfiguration rEnvConfig= null; |
| if (defaultConfigId != null && !defaultConfigId.isEmpty()) { |
| rEnvConfig= this.idMap.get(defaultConfigId); |
| } |
| String name; |
| REnv rEnv; |
| if (rEnvConfig != null) { |
| name= rEnvConfig.getName(); |
| rEnv= rEnvConfig.getREnv(); |
| } |
| else if (this.idMap.size() > 0) { |
| name= Messages.REnvManager_status_NoDefault_label; |
| rEnv= null; |
| } |
| else { |
| name= Messages.REnvManager_status_NotAny_label; |
| rEnv= null; |
| } |
| this.defaultEnv.name= name; |
| this.defaultEnv.link= rEnv; |
| } |
| |
| private void load() throws BackingStoreException { |
| this.idMap= new HashMap<>(); |
| |
| loadFromRegistry(); |
| loadFromWorkspace(); |
| } |
| |
| private void loadFromWorkspace() throws BackingStoreException { |
| final PreferenceAccess prefs= PreferenceUtils.getInstancePrefs(); |
| final List<IREnvConfiguration>configs= new ArrayList<>(); |
| |
| final Integer version= prefs.getPreferenceValue(PREF_VERSION); |
| if (version == null || version.intValue() == 0) { |
| for (final Iterator<IScopeContext> iter= prefs.getPreferenceContexts().iterator(); |
| configs.isEmpty() && iter.hasNext(); ) { |
| final IEclipsePreferences prefNode= iter.next().getNode(IREnvManager.PREF_QUALIFIER); |
| if (prefNode == null) { |
| continue; |
| } |
| final String[] names= prefNode.childrenNames(); |
| for (final String name : names) { |
| final Preferences node= prefNode.node(name); |
| final String id= node.get("id", null); //$NON-NLS-1$ |
| if (id != null && id.length() > 0 && id.startsWith(USER_ENV_PREFIX)) { |
| final ActualREnv rEnv= new ActualREnv(id); |
| final REnvConfigurationImpl config= new REnvConfigurationImpl( |
| null, rEnv, prefs, name); |
| if (config.getName() != null) { |
| config.upgradePref(); |
| rEnv.name= config.getName(); |
| rEnv.config= config; |
| this.idMap.put(id, config); |
| } |
| } |
| } |
| } |
| } |
| else if (version.intValue() == 2) { |
| for (final Iterator<IScopeContext> iter= prefs.getPreferenceContexts().iterator(); |
| configs.isEmpty() && iter.hasNext(); ) { |
| final IEclipsePreferences prefNode= iter.next().getNode(IREnvManager.PREF_QUALIFIER); |
| if (prefNode == null) { |
| continue; |
| } |
| final String[] names= prefNode.childrenNames(); |
| for (final String id : names) { |
| if (id != null && id.length() > 0 && id.startsWith(USER_ENV_PREFIX)) { |
| final ActualREnv rEnv= new ActualREnv(id); |
| final REnvConfigurationImpl config= new REnvConfigurationImpl( |
| null, rEnv, prefs, null); |
| if (config.getName() != null) { |
| rEnv.name= config.getName(); |
| rEnv.config= config; |
| this.idMap.put(id, config); |
| } |
| } |
| } |
| } |
| } |
| |
| // init default config |
| updateDefault(prefs.getPreferenceValue(PREF_DEFAULT_CONFIGURATION_NAME)); |
| } |
| |
| private void saveToWorkspace(final boolean envPrefs) throws BackingStoreException { |
| final IScopeContext context= PreferenceUtils.getInstancePrefs().getPreferenceContexts().get(0); |
| final IEclipsePreferences node= context.getNode(IREnvManager.PREF_QUALIFIER); |
| |
| final Map<Preference<?>, Object> map= new HashMap<>(); |
| map.put(PREF_VERSION, 2); |
| map.put(STAMP_PREF, System.currentTimeMillis()); |
| |
| if (envPrefs) { |
| final List<String> oldNames= new ArrayList<>(Arrays.asList(node.childrenNames())); |
| oldNames.removeAll(this.idMap.keySet()); |
| for (final String name : oldNames) { |
| if (node.nodeExists(name)) { |
| node.node(name).removeNode(); |
| } |
| } |
| |
| for (final IREnvConfiguration config : this.idMap.values()) { |
| if (config instanceof REnvConfigurationImpl |
| && !config.getType().startsWith("contrib.temp-") ) { |
| ((REnvConfigurationImpl) config).deliverToPreferencesMap(map); |
| } |
| } |
| } |
| { // default |
| final REnv rEnv= getActualREnv(this.defaultEnv); |
| map.put(PREF_DEFAULT_CONFIGURATION_NAME, (rEnv != null) ? rEnv.getId() : null); |
| } |
| |
| PreferenceUtils.setPrefValues(InstanceScope.INSTANCE, map); |
| node.flush(); |
| } |
| |
| private void loadFromRegistry() { |
| final PreferenceAccess prefs= PreferenceUtils.getInstancePrefs(); |
| final List<RSetup> setups= RSetupUtil.loadAvailableSetups(null); |
| for (final RSetup setup : setups) { |
| final ActualREnv rEnv= new ActualREnv(IREnvConfiguration.EPLUGIN_LOCAL_TYPE + '-' + setup.getId()); |
| final REnvConfigurationImpl config= new REnvConfigurationImpl( |
| IREnvConfiguration.EPLUGIN_LOCAL_TYPE, rEnv, setup, prefs ); |
| |
| rEnv.name= config.getName(); |
| rEnv.config= config; |
| |
| this.idMap.put(rEnv.getId(), config); |
| } |
| } |
| |
| private void onChanged(final boolean envPrefs) throws BackingStoreException { |
| final PreferenceSetService preferenceSetService= PreferenceUtils.getPreferenceSetService(); |
| final String sourceId= "REnv" + System.identityHashCode(this); //$NON-NLS-1$ |
| final boolean resume= preferenceSetService.pause(sourceId); |
| try { |
| saveToWorkspace(envPrefs); |
| return; |
| } |
| finally { |
| if (resume) { |
| preferenceSetService.resume(sourceId); |
| } |
| } |
| } |
| |
| |
| @Override |
| public void set(final ImList<IREnvConfiguration> configs, final String defaultConfigId) |
| throws CoreException { |
| checkAndLock(true); |
| try { |
| final boolean changed= update(configs, defaultConfigId); |
| if (!changed) { |
| return; |
| } |
| |
| onChanged(true); |
| } |
| catch (final BackingStoreException e) { |
| throw new CoreException(new Status(IStatus.ERROR, RCore.BUNDLE_ID, -1, Messages.REnvManager_error_Saving_message, e)); |
| } |
| finally { |
| this.lock.writeLock().unlock(); |
| } |
| } |
| |
| @Override |
| public void add(final @NonNull REnvConfiguration rEnvConfig) { |
| final REnv rEnv= rEnvConfig.getREnv(); |
| if (!rEnv.getId().startsWith(CONTRIB_TEMP_REMOTE_ENV_ID_PREFIX)) { |
| throw new IllegalArgumentException(); |
| } |
| |
| checkAndLock(true); |
| try { |
| boolean changed= false; |
| changed= doAdd((IREnvConfiguration) rEnvConfig, changed); |
| if (!changed) { |
| return; |
| } |
| |
| changed= doRefreshDefault(changed); |
| |
| onChanged(false); |
| } |
| catch (final BackingStoreException e) { |
| RCorePlugin.logError(Messages.REnvManager_error_Saving_message, e); |
| } |
| finally { |
| this.lock.writeLock().unlock(); |
| } |
| } |
| |
| @Override |
| public void delete(final @NonNull REnv rEnv) { |
| if (!rEnv.getId().startsWith(CONTRIB_TEMP_REMOTE_ENV_ID_PREFIX)) { |
| throw new IllegalArgumentException(); |
| } |
| |
| checkAndLock(true); |
| try { |
| boolean changed= false; |
| changed= doDelete(rEnv.getId(), changed); |
| if (!changed) { |
| return; |
| } |
| |
| changed= doRefreshDefault(changed); |
| |
| onChanged(false); |
| } |
| catch (final BackingStoreException e) { |
| RCorePlugin.logError(Messages.REnvManager_error_Saving_message, e); |
| } |
| finally { |
| this.lock.writeLock().unlock(); |
| } |
| } |
| |
| // public String[] getNames() { |
| // checkAndLock(false); |
| // try { |
| // return this.names.toArray(new String[this.names.size()]); |
| // } |
| // finally { |
| // this.lock.readLock().unlock(); |
| // } |
| // } |
| |
| @Override |
| public ImList<REnv> list() { |
| final List<IREnvConfiguration> configList; |
| checkAndLock(false); |
| try { |
| configList= new ArrayList<>(this.idMap.values()); |
| } |
| finally { |
| this.lock.readLock().unlock(); |
| } |
| final REnv[] list= new REnv[configList.size()]; |
| int i= 0; |
| for (final IREnvConfiguration config : configList) { |
| final REnv rEnv= config.getREnv(); |
| if (!rEnv.isDeleted()) { |
| list[i++]= rEnv; |
| } |
| } |
| return ImCollections.newList(list, 0, i); |
| } |
| |
| @Override |
| public List<IREnvConfiguration> getConfigurations() { |
| final List<IREnvConfiguration> list; |
| checkAndLock(false); |
| try { |
| list= new ArrayList<>(this.idMap.values()); |
| } |
| finally { |
| this.lock.readLock().unlock(); |
| } |
| final Iterator<IREnvConfiguration> iter= list.iterator(); |
| while (iter.hasNext()) { |
| if (iter.next().getREnv().isDeleted()) { |
| iter.remove(); |
| } |
| } |
| return list; |
| } |
| |
| public String[] getIds() { |
| checkAndLock(false); |
| try { |
| final Collection<String> keys= this.idMap.keySet(); |
| return keys.toArray(new String[keys.size()]); |
| } |
| finally { |
| this.lock.readLock().unlock(); |
| } |
| } |
| |
| @Override |
| public synchronized @Nullable REnv get(final String id, final String name) { |
| checkAndLock(false); |
| try { |
| if (id != null) { |
| final REnv rEnv= getEnv(id); |
| if (rEnv != null) { |
| return rEnv; |
| } |
| } |
| if (name != null) { |
| return getEnvByName(name); |
| } |
| return null; |
| } |
| finally { |
| this.lock.readLock().unlock(); |
| } |
| } |
| |
| private @Nullable REnv getEnv(final String id) { |
| if (id.equals(RCore.DEFAULT_WORKBENCH_ENV_ID)) { |
| return this.defaultEnv; |
| } |
| IREnvConfiguration config= this.idMap.get(id); |
| if (config == null) { |
| config= this.idMap.get(id); |
| } |
| if (config != null) { |
| return config.getREnv(); |
| } |
| return null; |
| } |
| |
| private @Nullable REnv getEnvByName(final String name) { |
| for (final REnvConfiguration config : this.idMap.values()) { |
| if (config.getName().equals(name)) { |
| return config.getREnv(); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public REnv getDefault() { |
| checkAndLock(false); |
| try { |
| return this.defaultEnv; |
| } |
| finally { |
| this.lock.readLock().unlock(); |
| } |
| } |
| |
| @Override |
| public WorkingCopy newConfiguration(final String type) { |
| return new REnvConfigurationImpl.Editable(type, newLink(type)); |
| } |
| |
| private ActualREnv newLink(final String type) { |
| return new ActualREnv(type + '-' + Long.toString( |
| ((long) System.getProperty("user.name").hashCode() << 32) | System.currentTimeMillis(), //$NON-NLS-1$ |
| 36 )); |
| } |
| |
| } |