/******************************************************************************* * 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. } }