/*******************************************************************************
* Copyright 2011 Google Inc. All Rights Reserved.
*
* 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
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package com.google.gdt.eclipse.suite.preferences;
import com.google.gdt.eclipse.core.CorePluginLog;
import com.google.gdt.eclipse.core.PropertiesUtilities;
import com.google.gdt.eclipse.core.sdk.SdkRegistrant;
import com.google.gdt.eclipse.suite.GdtPlugin;
import com.google.gwt.eclipse.core.sdk.GWTSdkRegistrant;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.PluginVersionIdentifier;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.osgi.service.datalocation.Location;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Version;
import org.osgi.service.prefs.BackingStoreException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Contains static methods for retrieving and setting GDT Plugin Preferences.
*/
@SuppressWarnings("deprecation")
public final class GdtPreferences {
/*
* This preference key was changed for 1.2 since we now store a string array (of wizard IDs) instead of a boolean
* flag. If a user upgrades to 1.2 from 1.0 or 1.1, the old preference will remain in their workspace settings store,
* but will be ignored.
*/
private static final String ADDED_NEW_WIZARD_ACTIONS = "addedNewWizardActions1.2_";
/**
* Key used to store the installation id.
*/
private static final String INSTALLATION_ID = "id";
/**
* The prefix of the key which stores the version associated with the last update notification that the user
* acknowledged for a given feature. The suffix of the key is the feature id.
*/
private static final String LAST_ACKNOWLEDGED_UPDATE_NOTIFICATION_PREFIX = "lastAckUpdateNotification_";
/**
* Last update time of this feature.
*/
private static final String LAST_UPDATE_TIME_MILLIS = "lastUpdate";
/**
* Severities of our custom problem types. We only store severities that differ from the default, which allows us to
* add new problem types or modify the default severity of existing problem types without changing any code.
*/
private static final String PROBLEM_SEVERITIES = "problemSeverities";
/**
* Records the migrator version of the project for use by {@link com.google.gdt.eclipse.suite.ProjectMigrator}.
*/
private static final String PROJECT_MIGRATOR_VERSION = "projectMigratorVersion_";
/*
* SDK bundles are discovered using their ID pattern: "com.gwtplugins.*.eclipse.sdkbundle"
*/
private static final String SDK_BUNDLE_PREFIX = "com.gwtplugins.";
private static final String SDK_BUNDLE_SUFFIX = ".eclipse.sdkbundle";
/**
* The prefix for the key to use for storing the SdkRegistrants. Version 1 key was just "SdkRegistrants". Version 2
* key was "SdkRegistrants_installationpath" (see computeSdkRegistrantsKeyV2). Version 3 key is
* "SdkRegistrants_installationID" (see computeSdkRegistrantsKeyV3).
*/
private static final String SDK_REGISTRANTS_KEY_PREFIX = "SdkRegistrants";
/**
* key in the marker property that identifies the kind of SDK is present
*/
private static final String SDK_BUNDLE_MARKER_PROPERTY = "sdkType";
/**
* identifies the path local prefix in a bundle to the SDK (default value <SDKTYPE>_HOME is replaced at build time
* with the actual path)
*/
private static final String SDK_PATH_PREFIX_PROPERTY = "sdkBundlePath";
/**
* The property filename expected at the root of any plugin/bundle that should be probed for SDK registration.
*/
private static final String SDK_REGISTRANT_PROPERTY_FILE = "SdkBundleRegistrant.properties";
/**
* Controls whether the updates should be automatically downloaded.
*/
private static final String UPDATE_NOTIFICATIONS = "updateNotifications";
/**
* Capture analytics of the use of this plugin.
*/
private static final String CAPTURE_ANALYTICS = "captureAnalytics";
/**
* Records the GEP plugin version that most recently forced a rebuild on a project after install (to clean up and
* regenerate stale markers, etc.).
*/
private static final String VERSION_FOR_LAST_FORCED_REBUILD_PREFIX = "versionForLastForcedRebuild_";
public static boolean areUpdateNotificationsEnabled() {
return getConfigurationPreferences().getBoolean(UPDATE_NOTIFICATIONS, true);
}
public static boolean getCaptureAnalytics() {
return getConfigurationPreferences().getBoolean(CAPTURE_ANALYTICS, true);
}
public static List<String> getAddedNewWizardActionsForPerspective(String perspectiveId) {
IEclipsePreferences instancePrefs = getInstancePreferences();
return PropertiesUtilities.deserializeStrings(instancePrefs.get(ADDED_NEW_WIZARD_ACTIONS + perspectiveId, ""));
}
/**
* Gets the problem severities as an encoded string. See
* {@link com.google.gdt.eclipse.core.markers.GdtProblemSeverities} for details on how this string is decoded.
*/
public static String getEncodedProblemSeverities() {
IEclipsePreferences instancePrefs = getConfigurationPreferences();
return instancePrefs.get(PROBLEM_SEVERITIES, "");
}
/**
* Returns the installation id for this plugin, or <code>null</code> if the installation id has never been set.
*
* @return installation id for this plugin.
*/
public static String getInstallationId() {
return getConfigurationPreferences().get(INSTALLATION_ID, null);
}
/**
* Returns the last update time in milliseconds for this installation or <code>0</code> if the last update time has
* never been set.
*
* @return the time in millis
*/
public static long getLastUpdateTimeMillis() {
return getConfigurationPreferences().getLong(LAST_UPDATE_TIME_MILLIS, 0);
}
public static int getProjectMigratorVersion(IProject project) {
IEclipsePreferences instancePrefs = getInstancePreferences();
return instancePrefs.getInt(PROJECT_MIGRATOR_VERSION + project.getName(), 0);
}
@SuppressWarnings("deprecation")
public static PluginVersionIdentifier getVersionForLastAcknowledgedUpdateNotification(String featureId) {
return new PluginVersionIdentifier(
getConfigurationPreferences().get(getLastAckFeatureUpdateVersionKey(featureId), "0.0.0.0"));
}
public static Version getVersionForLastForcedRebuild(IProject project) {
IEclipsePreferences instancePrefs = getInstancePreferences();
String versionString = instancePrefs.get(VERSION_FOR_LAST_FORCED_REBUILD_PREFIX + project.getName(), "0.0.0.0");
return new Version(versionString);
}
/**
* Registers all of the {@link SdkRegistrant}s, and records as a workspace preference which ones those were.
* Registrants will only be called once per workspace.
*
* Prior versions of the SDK registration mechanism required adding implementations of SdkBundleRegistratant that were
* aware of GPE internals, and would handle registrations themselves. To decouple the build/release process of
* sdkbundles from GPE, this approach has been abandoned.
*
* This implementation inspects bundles with a com.google.*.eclipse.sdkbundle bundle id and looks for a marker
* property file for details about the SDK. If the information resolves as a known SDK type and a valid SDK path, the
* SDK path is then registered against the proper registrant.
*
*/
public static synchronized void registerSdks() {
IEclipsePreferences instancePrefs = getInstancePreferences();
final String sdkRegistrantsKeyV3 = computeSdkRegistrantsKeyV3();
ensureUsingNewSdkRegistrantsKey(instancePrefs, sdkRegistrantsKeyV3);
String sdkRegistrantsAsString = instancePrefs.get(sdkRegistrantsKeyV3, "");
Set<String> sdkRegistrants = new LinkedHashSet<String>(decodeRegistrants(sdkRegistrantsAsString));
BundleContext context = GdtPlugin.getDefault().getBundle().getBundleContext();
for (Bundle bundle : context.getBundles()) {
String bundleName = bundle.getSymbolicName();
String bundleVersion = bundle.getHeaders().get(org.osgi.framework.Constants.BUNDLE_VERSION);
String sdkId = bundleName + '_' + bundleVersion;
// The bundle name must match com.google.*.eclipse.sdkbundle
if (bundleName.startsWith(SDK_BUNDLE_PREFIX) && bundleName.contains(SDK_BUNDLE_SUFFIX)
&& !sdkRegistrants.contains(sdkId)) {
GdtPlugin.getLogger().logInfo("Registering: " + sdkId);
try {
registerBundleSdk(bundle);
} catch (CoreException e) {
// Log and continue.
GdtPlugin.getLogger().logError(e);
}
// Add the sdk even if we get an exception while registering to prevent
// logging an error on each restart.
sdkRegistrants.add(sdkId);
}
}
instancePrefs.put(sdkRegistrantsKeyV3, encodeRegistrants(sdkRegistrants));
flushPreferences(instancePrefs);
}
public static void setAddedNewWizardActionsForPerspective(String perspectiveId, List<String> wizardIds) {
IEclipsePreferences instancePrefs = getInstancePreferences();
instancePrefs.put(ADDED_NEW_WIZARD_ACTIONS + perspectiveId, PropertiesUtilities.serializeStrings(wizardIds));
flushPreferences(instancePrefs);
}
/**
* Sets the problem severities using an encoded string generated by
* {@link com.google.gdt.eclipse.core.markers.GdtProblemSeverities#toPreferenceString()
* GdtProblemSeverities#toPreferenceString()}.
*/
public static void setEncodedProblemSeverities(String encodedSeverities) {
IEclipsePreferences configurationPreferences = getConfigurationPreferences();
configurationPreferences.put(PROBLEM_SEVERITIES, encodedSeverities);
flushPreferences(configurationPreferences);
}
public static void setInstallationId(String id) {
IEclipsePreferences configurationPreferences = getConfigurationPreferences();
configurationPreferences.put(INSTALLATION_ID, id);
flushPreferences(configurationPreferences);
}
/**
* Sets the last update time in milliseconds.
*
* @param lastUpdateTimeMillis
* date of the last update time in milliseconds
*/
public static void setLastUpdateTimeMillis(long lastUpdateTimeMillis) {
IEclipsePreferences configurationPreferences = getConfigurationPreferences();
configurationPreferences.putLong(LAST_UPDATE_TIME_MILLIS, lastUpdateTimeMillis);
flushPreferences(configurationPreferences);
}
public static void setProjectMigratorVersion(IProject project, int version) {
IEclipsePreferences configurationPreferences = getConfigurationPreferences();
configurationPreferences.putInt(PROJECT_MIGRATOR_VERSION + project.getName(), version);
flushPreferences(configurationPreferences);
}
public static void setUpdateNotificationsEnabled(boolean enabled) {
IEclipsePreferences configurationPreferences = getConfigurationPreferences();
configurationPreferences.putBoolean(UPDATE_NOTIFICATIONS, enabled);
flushPreferences(configurationPreferences);
}
public static void setAnalytics(boolean capture) {
IEclipsePreferences configurationPreferences = getConfigurationPreferences();
configurationPreferences.putBoolean(CAPTURE_ANALYTICS, capture);
flushPreferences(configurationPreferences);
}
@SuppressWarnings("deprecation")
public static void setVersionForLastAcknowlegedUpdateNotification(String featureId, PluginVersionIdentifier version) {
IEclipsePreferences configurationPreferences = getConfigurationPreferences();
configurationPreferences.put(getLastAckFeatureUpdateVersionKey(featureId), version.toString());
flushPreferences(configurationPreferences);
}
public static void setVersionForLastForcedRebuild(IProject project, Version version) {
IEclipsePreferences instancePrefs = getInstancePreferences();
instancePrefs.put(VERSION_FOR_LAST_FORCED_REBUILD_PREFIX + project.getName(), version.toString());
flushPreferences(instancePrefs);
}
private static String computeSdkRegistrantsKeyV2() {
StringBuilder key = new StringBuilder(SDK_REGISTRANTS_KEY_PREFIX);
Location location = Platform.getInstallLocation();
if (location != null) {
URL locationUrl = location.getURL();
if (locationUrl != null) {
key.append('_').append(locationUrl.toString());
}
}
return key.toString();
}
/**
* Version 1 key was just "SdkRegistrants". Version 2 key was "SdkRegistrants_installationpath" (see
* computeSdkRegistrantsKeyV2 above). Version 3 key is "SdkRegistrants_installationID".
*/
private static String computeSdkRegistrantsKeyV3() {
return SDK_REGISTRANTS_KEY_PREFIX + "_" + getInstallationId();
}
private static List<String> decodeRegistrants(String encodedRegistrants) {
if (encodedRegistrants.length() == 0) {
return Collections.emptyList();
}
String[] registrantsAsArray = encodedRegistrants.split(",");
return Arrays.asList(registrantsAsArray);
}
private static String encodeRegistrants(Collection<String> registrants) {
StringBuilder sb = new StringBuilder();
boolean addComma = false;
for (String registrant : registrants) {
if (!addComma) {
addComma = true;
} else {
sb.append(",");
}
sb.append(registrant);
}
return sb.toString();
}
private static void ensureUsingNewSdkRegistrantsKey(IEclipsePreferences instancePrefs, String newKey) {
try {
List<String> keys = Arrays.asList(instancePrefs.keys());
if (!keys.contains(newKey)) {
String oldKey2;
// are we using the original "SdkRegistrants" key?
if (keys.contains(SDK_REGISTRANTS_KEY_PREFIX)) {
updateSdkRegistrantsKey(instancePrefs, SDK_REGISTRANTS_KEY_PREFIX, newKey);
} else if (keys.contains((oldKey2 = computeSdkRegistrantsKeyV2()))) {
// or are we using the 2nd version "SdkRegistrants_installationpath"
// key?
updateSdkRegistrantsKey(instancePrefs, oldKey2, newKey);
}
}
} catch (BackingStoreException e) {
CorePluginLog.logError(e, "Could not check if migration to new SdkRegistrants key format is needed.");
}
}
private static void flushPreferences(IEclipsePreferences preferences) {
try {
preferences.flush();
} catch (BackingStoreException e) {
CorePluginLog.logError(e);
}
}
private static IEclipsePreferences getConfigurationPreferences() {
ConfigurationScope scope = new ConfigurationScope();
IEclipsePreferences configurationPrefs = scope.getNode(GdtPlugin.PLUGIN_ID);
return configurationPrefs;
}
/**
* Returns the instance scope context for this plugin.
*/
private static IEclipsePreferences getInstancePreferences() {
InstanceScope scope = new InstanceScope();
IEclipsePreferences instancePrefs = scope.getNode(GdtPlugin.PLUGIN_ID);
return instancePrefs;
}
private static String getLastAckFeatureUpdateVersionKey(String featureId) {
return LAST_ACKNOWLEDGED_UPDATE_NOTIFICATION_PREFIX + featureId;
}
/**
* Attempts to register the SDK from a bundle.
*/
private static void registerBundleSdk(Bundle bundle) throws CoreException {
try {
IPath propPath = new Path(SDK_REGISTRANT_PROPERTY_FILE);
URL propUrl = FileLocator.find(bundle, propPath, (Map<String, String>) null);
if (propUrl != null) {
InputStream instream = propUrl.openStream();
Properties props = new Properties();
props.load(instream);
String sdkType = props.getProperty(SDK_BUNDLE_MARKER_PROPERTY);
String sdkPrefix = props.getProperty(SDK_PATH_PREFIX_PROPERTY);
if (sdkType != null && sdkPrefix != null) {
IPath sdkPrefixPath = new Path(sdkPrefix);
URL sdkPathUrl = FileLocator.find(bundle, sdkPrefixPath, (Map<String, String>) null);
if (sdkPathUrl == null) {
// Automatic SDK registration failed. This is expected in dev mode.
CorePluginLog.logWarning("Failed to register SDK: " + sdkPrefix);
return;
}
// resolve needed to switch from bundleentry to file url
sdkPathUrl = FileLocator.resolve(sdkPathUrl);
if (sdkPathUrl != null) {
if ("file".equals(sdkPathUrl.getProtocol())) {
GWTSdkRegistrant.registerSdk(sdkPathUrl, sdkType);
}
}
}
}
} catch (IOException e) {
throw new CoreException(new Status(IStatus.WARNING, GdtPlugin.PLUGIN_ID, e.getLocalizedMessage(), e));
}
}
private static void updateSdkRegistrantsKey(IEclipsePreferences instancePrefs, String oldKey, String newKey) {
// Copy the oldKey's value into the newKey
instancePrefs.put(newKey, instancePrefs.get(oldKey, ""));
// Remove the oldKey so we do not copy it for future Eclipse
// installations
instancePrefs.remove(oldKey);
}
private GdtPreferences() {
// Not instantiable.
}
}