/******************************************************************************* * Copyright (c) 2014 Bruno Medeiros and other Contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package melnorme.lang.ide.core.utils.prefs; import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull; import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue; import java.util.HashMap; import java.util.Optional; import java.util.function.Supplier; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.runtime.preferences.DefaultScope; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; import org.eclipse.core.runtime.preferences.InstanceScope; import org.osgi.service.prefs.BackingStoreException; import melnorme.lang.ide.core.LangCore; import melnorme.lang.ide.core.PreferencesOverride; import melnorme.lang.ide.core.utils.ResourceUtils; import melnorme.lang.ide.core.utils.ResourceUtils.ProjectLocation; import melnorme.utilbox.core.CommonException; import melnorme.utilbox.fields.Field; import melnorme.utilbox.fields.IFieldView; public abstract class PreferenceHelper<T> implements IGlobalPreference<T> { protected static final HashMap<String, PreferenceHelper<?>> instances = new HashMap<>(); public static void checkUniqueKey(String key, PreferenceHelper<?> preference) { synchronized (instances) { // Ensure uniqueness: allow only one instance of a preference helper per key. assertTrue(instances.containsKey(key) == false); instances.put(key, preference); } } public final String key; public final String qualifier; protected T defaultValue; protected final IProjectPreference<Boolean> useProjectPreference; protected final Field<T> field = new Field<T>(); public PreferenceHelper(String key, T defaultValue) { this(LangCore.PLUGIN_ID, key, defaultValue); } public PreferenceHelper(String pluginId, String key, T defaultValue) { this(pluginId, key, defaultValue, true, null); } public PreferenceHelper(String pluginId, String key, T defaultValue, IProjectPreference<Boolean> useProjectPref) { this(pluginId, key, defaultValue, true, useProjectPref); } public PreferenceHelper(String key, T defaultValue, IProjectPreference<Boolean> useProjectPref) { this(LangCore.PLUGIN_ID, key, defaultValue, true, useProjectPref); } public PreferenceHelper(String pluginId, String key, T defaultValue, boolean ensureUniqueKey, IProjectPreference<Boolean> useProjectPreference) { this.key = assertNotNull(PreferencesOverride.getKeyIdentifer(key, this)); if(ensureUniqueKey) { checkUniqueKey(key, this); } this.qualifier = pluginId; this.useProjectPreference = useProjectPreference; // can be null setPreferencesDefaultValue(defaultValue); updateFieldFromPrefStore(); initializeListeners(); } public final String getQualifier() { return qualifier; } @Override public T getDefaultValue() { return defaultValue; } @Override public IFieldView<T> asField() { return field; } /* ----------------- ----------------- */ public void setPreferencesDefaultValue(T defaultValue) { this.defaultValue = defaultValue; setPrefValue(getDefaultNode(), defaultValue); updateFieldFromPrefStore(); } protected void updateFieldFromPrefStore() { field.setFieldValue(getFromPrefStore()); } protected IPreferencesAccess prefScopes() { return new PreferencesLookupHelper(getQualifier()); } protected IPreferencesAccess prefScopes(Optional<IProject> project) { return new PreferencesLookupHelper(getQualifier(), project); } protected IEclipsePreferences getDefaultNode() { return DefaultScope.INSTANCE.getNode(getQualifier()); } public final T getFromPrefStore() { return getPrefValue(prefScopes()); } protected T getPrefValue(IPreferencesAccess prefsAccess) { return getPrefValue(prefsAccess.getString(key)); } protected T getPrefValue(String savedValue) { return assertNotNull(parseString(savedValue)); } protected void setPrefValue(IEclipsePreferences preferences, T value) { if(value == null) { preferences.remove(key); } else { preferences.put(key, valueToString(value)); } } protected abstract T parseString(String stringValue); protected abstract String valueToString(T value); protected void initializeListeners() { InstanceScope.INSTANCE.getNode(qualifier).addPreferenceChangeListener(event -> handlePreferenceChange(event)); } @Override public final void setInstanceScopeValue(T value) throws CommonException { IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(getQualifier()); setPrefValue(prefs, value); try { prefs.flush(); } catch(BackingStoreException e) { throwCommonException(e); } } public static void throwCommonException(BackingStoreException e) throws CommonException { throw new CommonException(e.getMessage(), e.getCause()); } protected void handlePreferenceChange(PreferenceChangeEvent event) { if(event.getKey().equals(key)) { updateFieldFromPrefStore(); } } public interface IPreferencesAccess { public String getString(String key); } /* ----------------- ----------------- */ @Override public IProjectPreference<T> getProjectPreference() { return projectPreference; } protected final IProjectPreference<T> projectPreference = new IProjectPreference<T>() { @Override public PreferenceHelper<T> getGlobalPreference() { return PreferenceHelper.this; } @Override public T getDefaultValue() { return PreferenceHelper.this.getDefaultValue(); } @Override public Supplier<T> getEffectiveValueProperty(Optional<IProject> project) { return new Supplier<T>() { @Override public T get() { return getEffectiveValue(project); } }; } @Override public T getStoredValue(Optional<IProject> project) { return getProjectScopeValue(project); } @Override public void setValue(IProject project, T value) throws CommonException { doSetValue(project, value); flush(project); } @Override public void doSetValue(IProject project, T newValue) { assertNotNull(project); IEclipsePreferences projectPreferences = getProjectNode(project); try { ProjectLocation projLocHandle = ResourceUtils.locationHandle(project); LangCore.settings().notifySettingChanged(this, projLocHandle, newValue); } catch(CommonException e) { LangCore.logError("Error changing preference.", e); } setPrefValue(projectPreferences, newValue); } @Override public T getEffectiveValue(Optional<IProject> project) { assertNotNull(getEnableProjectSettingPref()); if(project != null && getEnableProjectSettingPref().getStoredValue(project) == true) { return getStoredValue(project); } return getGlobalPreference().get(); } @Override public IProjectPreference<Boolean> getEnableProjectSettingPref() { return useProjectPreference; } }; protected final T getProjectScopeValue(Optional<IProject> project) { return getPrefValue(prefScopes(project)); } protected void flush(IProject project) throws CommonException { try { getProjectNode(project).flush(); } catch(BackingStoreException e) { throwCommonException(e); } } protected IEclipsePreferences getProjectNode(IProject project) { return new ProjectScope(project).getNode(getQualifier()); } }