package org.eclipse.core.internal.preferences;
import java.io.*;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.*;
import org.eclipse.core.internal.preferences.exchange.IProductPreferencesService;
import org.eclipse.core.internal.runtime.RuntimeLog;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.preferences.BundleDefaultsScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
import org.osgi.util.tracker.ServiceTracker;
public class DefaultPreferences extends EclipsePreferences {
private static Set<String> loadedNodes = Collections.synchronizedSet(new HashSet<String>());
private static final String KEY_PREFIX = "%";
private static final String KEY_DOUBLE_PREFIX = "%%";
private static final IPath NL_DIR = new Path("$nl$");
private static final String PROPERTIES_FILE_EXTENSION = "properties";
private static Properties productCustomization;
private static Properties productTranslation;
private static Properties commandLineCustomization;
private EclipsePreferences loadLevel;
private Thread initializingThread;
private String qualifier;
private int segmentCount;
private WeakReference<Object> pluginReference;
public static String pluginCustomizationFile = null;
public DefaultPreferences() {
this(null, null);
}
private DefaultPreferences(EclipsePreferences parent, String name, Object context) {
this(parent, name);
this.pluginReference = new WeakReference<>(context);
}
private DefaultPreferences(EclipsePreferences parent, String name) {
super(parent, name);
if (parent instanceof DefaultPreferences)
this.pluginReference = ((DefaultPreferences) parent).pluginReference;
String path = absolutePath();
segmentCount = getSegmentCount(path);
if (segmentCount < 2)
return;
qualifier = getSegment(path, 1);
}
private void applyBundleDefaults() {
Bundle bundle = PreferencesOSGiUtils.getDefault().getBundle(name());
if (bundle == null)
return;
URL url = FileLocator.find(bundle, new Path(IPreferencesConstants.PREFERENCES_DEFAULT_OVERRIDE_FILE_NAME), null);
if (url == null) {
if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
PrefsMessages.message("Preference default override file not found for bundle: " + bundle.getSymbolicName());
return;
}
URL transURL = FileLocator.find(bundle, NL_DIR.append(IPreferencesConstants.PREFERENCES_DEFAULT_OVERRIDE_BASE_NAME).addFileExtension(PROPERTIES_FILE_EXTENSION), null);
if (transURL == null && EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
PrefsMessages.message("Preference translation file not found for bundle: " + bundle.getSymbolicName());
applyDefaults(name(), loadProperties(url), loadProperties(transURL));
}
private void applyCommandLineDefaults() {
if (commandLineCustomization != null)
applyDefaults(null, commandLineCustomization, null);
}
private void applyDefaults(String id, Properties defaultValues, Properties translations) {
for (Enumeration<?> e = defaultValues.keys(); e.hasMoreElements();) {
String fullKey = (String) e.nextElement();
String value = defaultValues.getProperty(fullKey);
if (value == null)
continue;
String localQualifier = id;
String fullPath = fullKey;
int firstIndex = fullKey.indexOf(PATH_SEPARATOR);
if (id == null && firstIndex > 0) {
localQualifier = fullKey.substring(0, firstIndex);
fullPath = fullKey.substring(firstIndex, fullKey.length());
}
String[] splitPath = decodePath(fullPath);
String childPath = splitPath[0];
childPath = makeRelative(childPath);
String key = splitPath[1];
if (name().equals(localQualifier)) {
value = translatePreference(value, translations);
if (EclipsePreferences.DEBUG_PREFERENCE_SET)
PrefsMessages.message("Setting default preference: " + (new Path(absolutePath()).append(childPath).append(key)) + '=' + value);
((EclipsePreferences) internalNode(childPath.toString(), false, null)).internalPut(key, value);
}
}
}
public IEclipsePreferences node(String childName, Object context) {
return internalNode(childName, true, context);
}
private boolean containsNode(Properties props, IPath path) {
if (props == null)
return false;
for (Enumeration<?> e = props.keys(); e.hasMoreElements();) {
String fullKey = (String) e.nextElement();
if (props.getProperty(fullKey) == null)
continue;
IPath nodePath = new Path(fullKey).removeLastSegments(1);
if (path.isPrefixOf(nodePath))
return true;
}
return false;
}
@Override
public boolean nodeExists(String path) throws BackingStoreException {
if (path.length() == 0 || path.charAt(0) == IPath.SEPARATOR)
return super.nodeExists(path);
if (super.nodeExists(path))
return true;
initializeCustomizations();
IPath scopeBasedPath = new Path(absolutePath() + PATH_SEPARATOR + path).removeFirstSegments(1);
return containsNode(productCustomization, scopeBasedPath) || containsNode(commandLineCustomization, scopeBasedPath);
}
private void initializeCustomizations() {
if (productCustomization == null) {
BundleContext context = Activator.getContext();
if (context != null) {
ServiceTracker<?, IProductPreferencesService> productTracker = new ServiceTracker<>(context, IProductPreferencesService.class, null);
productTracker.open();
IProductPreferencesService productSpecials = productTracker.getService();
if (productSpecials != null) {
productCustomization = productSpecials.getProductCustomization();
productTranslation = productSpecials.getProductTranslation();
}
productTracker.close();
} else {
PrefsMessages.message("Product-specified preferences called before plugin is started");
}
if (productCustomization == null)
productCustomization = new Properties();
}
if (commandLineCustomization == null) {
String filename = pluginCustomizationFile;
if (filename == null) {
if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
PrefsMessages.message("Command-line preferences customization file not specified.");
} else {
if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
PrefsMessages.message("Using command-line preference customization file: " + filename);
commandLineCustomization = loadProperties(filename);
}
}
}
private void applyRuntimeDefaults() {
WeakReference<Object> ref = PreferencesService.getDefault().applyRuntimeDefaults(name(), pluginReference);
if (ref != null)
pluginReference = ref;
}
private void applyProductDefaults() {
if (!productCustomization.isEmpty())
applyDefaults(null, productCustomization, productTranslation);
}
@Override
public void flush() {
}
@Override
protected IEclipsePreferences getLoadLevel() {
if (loadLevel == null) {
if (qualifier == null)
return null;
EclipsePreferences node = this;
for (int i = 2; i < segmentCount; i++)
node = (EclipsePreferences) node.parent();
loadLevel = node;
}
return loadLevel;
}
@Override
protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) {
return new DefaultPreferences(nodeParent, nodeName, context);
}
@Override
protected boolean isAlreadyLoaded(IEclipsePreferences node) {
return loadedNodes.contains(node.name());
}
@Override
protected void load() {
setInitializingBundleDefaults();
try {
applyRuntimeDefaults();
applyBundleDefaults();
} finally {
clearInitializingBundleDefaults();
}
initializeCustomizations();
applyProductDefaults();
applyCommandLineDefaults();
}
@Override
protected String internalPut(String key, String newValue) {
String result = super.internalPut(key, newValue);
if (isInitializingBundleDefaults()) {
String relativePath = getScopeRelativePath(absolutePath());
if (relativePath != null) {
Preferences node = PreferencesService.getDefault().getRootNode().node(BundleDefaultsScope.SCOPE).node(relativePath);
node.put(key, newValue);
}
}
return result;
}
private void setInitializingBundleDefaults() {
IEclipsePreferences node = getLoadLevel();
if (node instanceof DefaultPreferences) {
DefaultPreferences loader = (DefaultPreferences) node;
loader.initializingThread = Thread.currentThread();
}
}
private void clearInitializingBundleDefaults() {
IEclipsePreferences node = getLoadLevel();
if (node instanceof DefaultPreferences) {
DefaultPreferences loader = (DefaultPreferences) node;
loader.initializingThread = null;
}
}
private boolean isInitializingBundleDefaults() {
IEclipsePreferences node = getLoadLevel();
if (node instanceof DefaultPreferences) {
DefaultPreferences loader = (DefaultPreferences) node;
return loader.initializingThread == Thread.currentThread();
}
return false;
}
protected static String getScopeRelativePath(String absolutePath) {
if (absolutePath.length() < 2)
return null;
int index = absolutePath.indexOf('/', 1);
if (index == -1 || index + 1 >= absolutePath.length())
return null;
return absolutePath.substring(index + 1);
}
private Properties loadProperties(URL url) {
Properties result = new Properties();
if (url == null)
return result;
InputStream input = null;
try {
input = url.openStream();
result.load(input);
} catch (IOException | IllegalArgumentException e) {
if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL) {
PrefsMessages.message("Problem opening stream to preference customization file: " + url);
e.printStackTrace();
}
}
finally {
if (input != null)
try {
input.close();
} catch (IOException e) {
}
}
return result;
}
private Properties loadProperties(String filename) {
Properties result = new Properties();
InputStream input = null;
try {
input = new BufferedInputStream(new FileInputStream(filename));
result.load(input);
} catch (FileNotFoundException e) {
if (EclipsePreferences.DEBUG_PREFERENCE_GENERAL)
PrefsMessages.message("Preference customization file not found: " + filename);
} catch (IOException e) {
String message = NLS.bind(PrefsMessages.preferences_loadException, filename);
IStatus status = new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, e);
RuntimeLog.log(status);
} catch (IllegalArgumentException e) {
String message = NLS.bind(PrefsMessages.preferences_loadException, filename);
IStatus status = new Status(IStatus.ERROR, PrefsMessages.OWNER_NAME, IStatus.ERROR, message, e);
RuntimeLog.log(status);
} finally {
if (input != null)
try {
input.close();
} catch (IOException e) {
}
}
return result;
}
@Override
protected void loaded() {
loadedNodes.add(name());
}
@Override
public void sync() {
}
private String translatePreference(String origValue, Properties props) {
if (props == null || origValue.startsWith(KEY_DOUBLE_PREFIX))
return origValue;
if (origValue.startsWith(KEY_PREFIX)) {
String value = origValue.trim();
int ix = value.indexOf(" ");
String key = ix == -1 ? value.substring(1) : value.substring(1, ix);
String dflt = ix == -1 ? value : value.substring(ix + 1);
return props.getProperty(key, dflt);
}
return origValue;
}
}