/******************************************************************************* * Copyright (c) 2007, 2013 Borland Software Corporation and others. * * 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: * Borland Software Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.m2m.internal.qvt.oml.common.ui.wizards; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import org.eclipse.jface.dialogs.IDialogSettings; import org.eclipse.jface.wizard.Wizard; import org.eclipse.ui.plugin.AbstractUIPlugin; /** * An abstract base class for all wizads supporting persistent value storage * opportunities: values enetered in last success wizard call might be stored and loaded * in further calls * * Each wizard is thought about as started in some <i>context</i>, that is some environment * depending on which default values are loaded into wizard. For example if we start an Apply * transformation wizard on a UML1.4 project we load last successful values for this type of * projects. "project.type=UML1.4" is a key value identifying the context. * * @author abreslav */ public abstract class PersistedValuesWizard extends Wizard { /** * @param plugin - UI plugin which ownes this wizard (used to store dialog settings) */ public PersistedValuesWizard(AbstractUIPlugin plugin) { super(); myPlugin = plugin; } /** * Called in {@link #performFinish <code>performFinish</code>} when result of * {@link #performFinishBody <code>performFinishBody()</code>} is <code>true</code> * Override this method to save all the stored values into storage */ protected abstract void saveValues(); /** * Override this method instead of {@link #performFinish <code>performFinish</code>} */ protected abstract boolean performFinishBody(); /** * Final to force descendants to use saveValues on successful finish * override {@link #performFinishBody <code>performFinishBody</code>} */ @Override public final boolean performFinish() { boolean result = performFinishBody(); if (result) { saveValues(); } return result; } /** * Loads a set of preferences corresponding to the given keys * @param keys - a <code>Map</code> of type String => String (<code>key => value</code>) where the <code>key</code> is name of a key preference * and <code>value</code> if it's value * @return a section with preferences which fits the key, if no section fits the key excatly * but some section just doesn't have some of key entries this section is returned (with * lost entries added), if none found a new section is created and with key values set */ public PreferenceSection loadValues(Map<String, ?> keys) { // The root section, corresponding to the wizard IDialogSettings section = myPlugin.getDialogSettings().getSection(getClass().getName()); if (section == null) { section = myPlugin.getDialogSettings().addNewSection(getClass().getName()); } // Different wizard configurations String name = "0"; //$NON-NLS-1$ IDialogSettings[] sections = section.getSections(); PreferenceSection result; if ((sections == null) || (sections.length == 0)) { result = new PreferenceSection(section, name); } else { IDialogSettings resSection = null; // Looking for a section satisfying the key with the latest timestamp for (int i = 0; i < sections.length; i++) { int equal = checkSectionEquals(sections[i], keys); switch (equal) { case SECTIONS_EQUAL: if ((resSection == null) || (resSection.getLong(TIMESTAMP) < sections[i].getLong(TIMESTAMP))) { resSection = sections[i]; } break; case SECTIONS_EQUAL_WITH_IMPLIED: if (resSection == null) { resSection = sections[i]; } break; } } if (resSection == null) { result = getFreeSubsection(section); } else { result = new PreferenceSection(resSection, PreferenceSection.DONT_ERASE_DATA); } } initSection(result, keys); return result; } protected PreferenceSection loadValues(String key, String value) { Map<String, String> keys = new HashMap<String, String>(); keys.put(key, value); return loadValues(keys); } protected PreferenceSection loadValues(String key1, String value1, String key2, String value2) { Map<String, String> keys = new HashMap<String, String>(); keys.put(key1, value1); keys.put(key2, value2); return loadValues(keys); } /** * Finds a free subsection of the given section. If the maximum number of sections * isn't reached, cretes a new subsection, otherwise returns the most old subsection * considering it to be replaced * @param section - a root section * @return a reference to a free section object */ private PreferenceSection getFreeSubsection(IDialogSettings section) { IDialogSettings[] subsections = section.getSections(); if (subsections == null) { return new PreferenceSection(section, "0"); //$NON-NLS-1$ } if (subsections.length < MAX_RECORDS) { return new PreferenceSection(section, String.valueOf(subsections.length)); } IDialogSettings toReplace = subsections[0]; for (int i = 1; i < subsections.length; i++) { if (toReplace.getLong(TIMESTAMP) > subsections[i].getLong(TIMESTAMP)) { toReplace = subsections[i]; } } return new PreferenceSection(toReplace, PreferenceSection.ERASE_DATA); } /** * Checks whether the section given has the same key values as set by #putKey() * calls. * @param section - the section to be checked * @return <code>true</code> for equal keys, <code>false</code> - otherwise */ private static final int SECTIONS_DIFFERENT = -1; private static final int SECTIONS_EQUAL = 0; private static final int SECTIONS_EQUAL_WITH_IMPLIED = 1; private int checkSectionEquals(IDialogSettings section, Map<String,?> keys) { int result = SECTIONS_EQUAL; for (Iterator<String> i = keys.keySet().iterator(); i.hasNext();) { String key = (String) i.next(); String value = section.get(key); if (!keys.get(key).equals(value)) { if (value == null) { result = SECTIONS_EQUAL_WITH_IMPLIED; } else { return SECTIONS_DIFFERENT; } } } return result; } /** * Initializes newly loaded section: stores key valus and sets a time stamp * @param section */ private void initSection(PreferenceSection section, Map<String, ?> keys) { section.put(TIMESTAMP, new Date().getTime()); for (Iterator<String> i = keys.keySet().iterator(); i.hasNext();) { String key = (String) i.next(); section.put(key, (String) keys.get(key)); } } /** * A wrapper class for IDialogSettings * @author abreslav */ protected class PreferenceSection { private PreferenceSection(IDialogSettings section, boolean eraseData) { mySection = section; myErased = eraseData; } private PreferenceSection(IDialogSettings parent, String name) { myParent = parent; myName = name; mySection = null; myErased = false; } public void put(String key, String value) { myCache.put(key, value); } public void put(String key, boolean value) { myCache.put(key, new Boolean(value)); } public void put(String key, long value) { myCache.put(key, new Long(value)); } public void put(String key, Map<String, String> value) { String[] values = new String[value.size() * 2]; int i = 0; for (Entry<String, String> entry : value.entrySet()) { values[i] = entry.getKey(); values[i + 1] = entry.getValue(); i += 2; } myCache.put(key, values); } public String get(String key) { return get(key, null); } public String get(String key, String defaultValue) { String result = defaultValue; if (!myCache.containsKey(key)) { if (!myErased && mySection != null) { result = mySection.get(key); myCache.put(key, result); } } else { result = (String) myCache.get(key); } return result; } public boolean getBoolean(String key) { return getBoolean(key, false); } public boolean getBoolean(String key, boolean defaultValue) { boolean result = defaultValue; if (!myCache.containsKey(key)) { if (!myErased && mySection != null) { result = mySection.getBoolean(key); myCache.put(key, new Boolean(result)); } } else { result = ((Boolean) myCache.get(key)).booleanValue(); } return result; } @SuppressWarnings("unchecked") public Map<String, String> getStringMap(String key) { Map<String, String> result = Collections.<String, String>emptyMap(); if (!myCache.containsKey(key)) { if (!myErased && mySection != null) { String[] values = mySection.getArray(key); if (values != null) { result = new HashMap<String, String>(); for (int i = 0; i < values.length; i += 2) { result.put(values[i], (i + 1 < values.length) ? values[i + 1] : null); } myCache.put(key, result); } } } else { result = (Map<String, String>) myCache.get(key); } return result; } public void save() { if (mySection == null) { mySection = myParent.addNewSection(myName); } for (Entry<String, Object> entry : myCache.entrySet()) { if (entry.getValue() instanceof String[]) { String[] values = (String[]) entry.getValue(); mySection.put(entry.getKey(), values); } else if (entry.getValue() != null) { mySection.put(entry.getKey().toString(), entry.getValue().toString()); } } } private IDialogSettings myParent; private String myName; private IDialogSettings mySection; private final Map<String, Object> myCache = new HashMap<String, Object>(); private final boolean myErased; public static final boolean ERASE_DATA = true; public static final boolean DONT_ERASE_DATA = false; } private final AbstractUIPlugin myPlugin; public final String TIMESTAMP = "org.eclipse.m2m.internal.qvt.oml.common.wizards.PersistedValuesWizard.timestamp"; //$NON-NLS-1$ /** * Maximum number of records for a single wizard type */ public final int MAX_RECORDS = 20; }