blob: 44c71f793d42970fd3584b23b3a03c3ba757a616 [file] [log] [blame]
/*=============================================================================#
# 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 ));
}
}