Changeset 18833 in josm for trunk


Ignore:
Timestamp:
2023-09-20T15:28:52+02:00 (18 months ago)
Author:
taylor.smock
Message:

Fix #17052: Allow plugins to save state to session file

The primary feature request was for the TODO plugin to save the list elements for
a future session.

This allows plugins to register via ServiceLoader classes which need to be
called to save or restore their state.

In addition, this fixes an ordering issue with tests whereby the OsmApi cache
would be cleared, but the FakeOsmApi class would not recache itself when called.

Location:
trunk
Files:
2 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/actions/SessionLoadAction.java

    r18466 r18833  
    1313import java.nio.file.StandardCopyOption;
    1414import java.util.Arrays;
     15import java.util.EnumSet;
    1516import java.util.List;
    1617
     
    3435import org.openstreetmap.josm.io.session.SessionReader.SessionProjectionChoiceData;
    3536import org.openstreetmap.josm.io.session.SessionReader.SessionViewportData;
     37import org.openstreetmap.josm.io.session.SessionWriter;
    3638import org.openstreetmap.josm.tools.CheckParameterUtil;
    3739import org.openstreetmap.josm.tools.JosmRuntimeException;
     
    206208                    viewport = reader.getViewport();
    207209                    projectionChoice = reader.getProjectionChoice();
    208                     SessionSaveAction.setCurrentSession(file, zip, reader.getLayers());
     210                    final EnumSet<SessionWriter.SessionWriterFlags> flagSet = EnumSet.noneOf(SessionWriter.SessionWriterFlags.class);
     211                    if (zip) {
     212                        flagSet.add(SessionWriter.SessionWriterFlags.IS_ZIP);
     213                    }
     214                    if (reader.loadedPluginData()) {
     215                        flagSet.add(SessionWriter.SessionWriterFlags.SAVE_PLUGIN_INFORMATION);
     216                    }
     217                    SessionSaveAction.setCurrentSession(file, reader.getLayers(), flagSet);
    209218                } finally {
    210219                    if (tempFile) {
  • trunk/src/org/openstreetmap/josm/actions/SessionSaveAction.java

    r18485 r18833  
    1818import java.util.Arrays;
    1919import java.util.Collection;
     20import java.util.EnumSet;
    2021import java.util.HashMap;
    2122import java.util.HashSet;
     
    5758import org.openstreetmap.josm.gui.util.WindowGeometry;
    5859import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
     60import org.openstreetmap.josm.io.session.PluginSessionExporter;
    5961import org.openstreetmap.josm.io.session.SessionLayerExporter;
    6062import org.openstreetmap.josm.io.session.SessionWriter;
     63import org.openstreetmap.josm.plugins.PluginHandler;
    6164import org.openstreetmap.josm.spi.preferences.Config;
    6265import org.openstreetmap.josm.tools.GBC;
     
    7982
    8083    private static final BooleanProperty SAVE_LOCAL_FILES_PROPERTY = new BooleanProperty("session.savelocal", true);
     84    private static final BooleanProperty SAVE_PLUGIN_INFORMATION_PROPERTY = new BooleanProperty("session.saveplugins", false);
    8185    private static final String TOOLTIP_DEFAULT = tr("Save the current session.");
    8286
     
    8993    static File sessionFile;
    9094    static boolean isZipSessionFile;
     95    private static boolean pluginData;
    9196    static List<WeakReference<Layer>> layersInSessionFile;
    9297
     
    171176
    172177        boolean zipRequired = layersOut.stream().map(l -> exporters.get(l))
    173                 .anyMatch(ex -> ex != null && ex.requiresZip());
     178                .anyMatch(ex -> ex != null && ex.requiresZip()) || pluginsWantToSave();
    174179
    175180        saveAs = !doGetFile(saveAs, zipRequired);
     
    241246        }
    242247
    243         SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, isZipSessionFile);
     248        final EnumSet<SessionWriter.SessionWriterFlags> flags = EnumSet.noneOf(SessionWriter.SessionWriterFlags.class);
     249        if (pluginData || (Boolean.TRUE.equals(SAVE_PLUGIN_INFORMATION_PROPERTY.get()) && pluginsWantToSave())) {
     250            flags.add(SessionWriter.SessionWriterFlags.SAVE_PLUGIN_INFORMATION);
     251        }
     252        if (isZipSessionFile) {
     253            flags.add(SessionWriter.SessionWriterFlags.IS_ZIP);
     254        }
     255        SessionWriter sw = new SessionWriter(layersOut, active, exporters, dependencies, flags.toArray(new SessionWriter.SessionWriterFlags[0]));
    244256        try {
    245257            Notification savingNotification = showSavingNotification(sessionFile.getName());
     
    435447            JCheckBox chkSaveLocal = new JCheckBox(tr("Save all local files to disk"), SAVE_LOCAL_FILES_PROPERTY.get());
    436448            chkSaveLocal.addChangeListener(l -> SAVE_LOCAL_FILES_PROPERTY.put(chkSaveLocal.isSelected()));
    437             op.add(chkSaveLocal);
     449            op.add(chkSaveLocal, GBC.eol());
     450            if (pluginsWantToSave()) {
     451                JCheckBox chkSavePlugins = new JCheckBox(tr("Save plugin information to disk"), SAVE_PLUGIN_INFORMATION_PROPERTY.get());
     452                chkSavePlugins.addChangeListener(l -> SAVE_PLUGIN_INFORMATION_PROPERTY.put(chkSavePlugins.isSelected()));
     453                chkSavePlugins.setToolTipText(tr("Plugins may have additional information that can be saved"));
     454                op.add(chkSavePlugins, GBC.eol());
     455            }
    438456            return op;
    439457        }
     
    510528     * @param zip if it is a zip session file
    511529     * @param layers layers that are currently represented in the session file
    512      */
     530     * @deprecated since 18833, use {@link #setCurrentSession(File, List, SessionWriter.SessionWriterFlags...)} instead
     531     */
     532    @Deprecated
    513533    public static void setCurrentSession(File file, boolean zip, List<Layer> layers) {
     534        if (zip) {
     535            setCurrentSession(file, layers, SessionWriter.SessionWriterFlags.IS_ZIP);
     536        } else {
     537            setCurrentSession(file, layers);
     538        }
     539    }
     540
     541    /**
     542     * Sets the current session file and the layers included in that file
     543     * @param file file
     544     * @param layers layers that are currently represented in the session file
     545     * @param flags The flags for the current session
     546     * @since 18833
     547     */
     548    public static void setCurrentSession(File file, List<Layer> layers, SessionWriter.SessionWriterFlags... flags) {
     549        final EnumSet<SessionWriter.SessionWriterFlags> flagSet = EnumSet.noneOf(SessionWriter.SessionWriterFlags.class);
     550        flagSet.addAll(Arrays.asList(flags));
     551        setCurrentSession(file, layers, flagSet);
     552    }
     553
     554    /**
     555     * Sets the current session file and the layers included in that file
     556     * @param file file
     557     * @param layers layers that are currently represented in the session file
     558     * @param flags The flags for the current session
     559     * @since 18833
     560     */
     561    public static void setCurrentSession(File file, List<Layer> layers, Set<SessionWriter.SessionWriterFlags> flags) {
    514562        setCurrentLayers(layers);
    515         setCurrentSession(file, zip);
     563        setCurrentSession(file, flags.contains(SessionWriter.SessionWriterFlags.IS_ZIP));
     564        pluginData = flags.contains(SessionWriter.SessionWriterFlags.SAVE_PLUGIN_INFORMATION);
    516565    }
    517566
     
    551600    }
    552601
     602    /**
     603     * Check to see if any plugins want to save their state
     604     * @return {@code true} if the plugin wants to save their state
     605     */
     606    private static boolean pluginsWantToSave() {
     607        for (PluginSessionExporter exporter : PluginHandler.load(PluginSessionExporter.class)) {
     608            if (exporter.requiresSaving()) {
     609                return true;
     610            }
     611        }
     612        return false;
     613    }
     614
    553615}
  • trunk/src/org/openstreetmap/josm/io/session/GenericSessionExporter.java

    r18466 r18833  
    3232import org.openstreetmap.josm.gui.widgets.JosmTextField;
    3333import org.openstreetmap.josm.io.session.SessionWriter.ExportSupport;
     34import org.openstreetmap.josm.plugins.PluginHandler;
    3435import org.openstreetmap.josm.tools.GBC;
    3536import org.openstreetmap.josm.tools.ImageProvider;
     
    211212    @Override
    212213    public boolean requiresZip() {
    213         return include.isSelected();
     214        if (include.isSelected()) {
     215            return true;
     216        }
     217        for (PluginSessionExporter exporter : PluginHandler.load(PluginSessionExporter.class)) {
     218            if (exporter.requiresSaving()) {
     219                return true;
     220            }
     221        }
     222        return false;
    214223    }
    215224
  • trunk/src/org/openstreetmap/josm/io/session/SessionReader.java

    r18494 r18833  
    4040import org.openstreetmap.josm.data.coor.LatLon;
    4141import org.openstreetmap.josm.data.projection.Projection;
     42import org.openstreetmap.josm.gui.ExceptionDialogUtil;
    4243import org.openstreetmap.josm.gui.ExtendedDialog;
    4344import org.openstreetmap.josm.gui.MainApplication;
     
    4546import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    4647import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     48import org.openstreetmap.josm.gui.util.GuiHelper;
    4749import org.openstreetmap.josm.io.Compression;
    4850import org.openstreetmap.josm.io.IllegalDataException;
     51import org.openstreetmap.josm.plugins.PluginHandler;
    4952import org.openstreetmap.josm.tools.CheckParameterUtil;
    5053import org.openstreetmap.josm.tools.JosmRuntimeException;
     
    157160    private URI sessionFileURI;
    158161    private boolean zip; // true, if session file is a .joz file; false if it is a .jos file
     162    private boolean pluginData; // true, if a plugin restored state from a .joz file. False otherwise.
    159163    private ZipFile zipFile;
    160164    private List<Layer> layers = new ArrayList<>();
     
    193197        if (importerClass == null)
    194198            return null;
    195         SessionLayerImporter importer = null;
     199        SessionLayerImporter importer;
    196200        try {
    197201            importer = importerClass.getConstructor().newInstance();
     
    242246    public SessionProjectionChoiceData getProjectionChoice() {
    243247        return projectionChoice;
     248    }
     249
     250    /**
     251     * Returns whether plugins loaded additonal data
     252     * @return {@code true} if at least one plugin loaded additional data
     253     * @since 18833
     254     */
     255    public boolean loadedPluginData() {
     256        return this.pluginData;
    244257    }
    245258
     
    309322        /**
    310323         * Return an InputStream for a URI from a .jos/.joz file.
    311          *
     324         * <p>
    312325         * The following forms are supported:
    313          *
     326         * <p>
    314327         * - absolute file (both .jos and .joz):
    315328         *         "file:///home/user/data.osm"
     
    352365        /**
    353366         * Return a File for a URI from a .jos/.joz file.
    354          *
     367         * <p>
    355368         * Returns null if the URI points to a file inside the zip archive.
    356369         * In this case, inZipPath will be set to the corresponding path.
     
    713726     * Show Dialog when there is an error for one layer.
    714727     * Ask the user whether to cancel the complete session loading or just to skip this layer.
    715      *
     728     * <p>
    716729     * This is expected to run in a worker thread (PleaseWaitRunnable), so invokeAndWait is
    717730     * needed to block the current thread and wait for the result of the modal dialog from EDT.
     
    743756    }
    744757
     758    private void loadPluginData() {
     759        if (!zip) {
     760            return;
     761        }
     762        for (PluginSessionImporter importer : PluginHandler.load(PluginSessionImporter.class)) {
     763            try {
     764                this.pluginData |= importer.readZipFile(zipFile);
     765            } catch (IOException ioException) {
     766                GuiHelper.runInEDT(() -> ExceptionDialogUtil.explainException(ioException));
     767            }
     768        }
     769    }
     770
    745771    /**
    746772     * Loads session from the given file.
     
    754780        try (InputStream josIS = createInputStream(sessionFile, zip)) {
    755781            loadSession(josIS, sessionFile.toURI(), zip, progressMonitor);
     782            this.postLoadTasks.add(this::loadPluginData);
    756783        }
    757784    }
  • trunk/src/org/openstreetmap/josm/io/session/SessionWriter.java

    r18466 r18833  
    1010import java.nio.file.Files;
    1111import java.util.Collection;
     12import java.util.EnumSet;
    1213import java.util.HashMap;
    1314import java.util.List;
     
    4445import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
    4546import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
     47import org.openstreetmap.josm.plugins.PluginHandler;
    4648import org.openstreetmap.josm.tools.JosmRuntimeException;
    4749import org.openstreetmap.josm.tools.Logging;
     
    5961public class SessionWriter {
    6062
     63    /**
     64     * {@link SessionWriter} options
     65     * @since 18833
     66     */
     67    public enum SessionWriterFlags {
     68        /**
     69         * Use if the file to be written needs to be a zip file
     70         */
     71        IS_ZIP,
     72        /**
     73         * Use if there are plugins that want to save information
     74         */
     75        SAVE_PLUGIN_INFORMATION
     76    }
     77
    6178    private static final Map<Class<? extends Layer>, Class<? extends SessionLayerExporter>> sessionLayerExporters = new HashMap<>();
    6279
     
    6683    private final MultiMap<Layer, Layer> dependencies;
    6784    private final boolean zip;
     85    private final boolean plugins;
    6886
    6987    private ZipOutputStream zipOut;
     
    83101    /**
    84102     * Register a session layer exporter.
    85      *
     103     * <p>
    86104     * The exporter class must have a one-argument constructor with layerClass as formal parameter type.
    87105     * @param layerClass layer class
     
    121139    public SessionWriter(List<Layer> layers, int active, Map<Layer, SessionLayerExporter> exporters,
    122140                MultiMap<Layer, Layer> dependencies, boolean zip) {
     141        this(layers, active, exporters, dependencies,
     142                zip ? new SessionWriterFlags[] {SessionWriterFlags.IS_ZIP} : new SessionWriterFlags[0]);
     143    }
     144
     145    /**
     146     * Constructs a new {@code SessionWriter}.
     147     * @param layers The ordered list of layers to save
     148     * @param active The index of active layer in {@code layers} (starts at 0). Ignored if set to -1
     149     * @param exporters The exporters to use to save layers
     150     * @param dependencies layer dependencies
     151     * @param flags The flags to use when writing data
     152     * @since 18833
     153     */
     154    public SessionWriter(List<Layer> layers, int active, Map<Layer, SessionLayerExporter> exporters,
     155                         MultiMap<Layer, Layer> dependencies, SessionWriterFlags... flags) {
    123156        this.layers = layers;
    124157        this.active = active;
    125158        this.exporters = exporters;
    126159        this.dependencies = dependencies;
    127         this.zip = zip;
     160        final EnumSet<SessionWriterFlags> flagSet = flags.length == 0 ? EnumSet.noneOf(SessionWriterFlags.class) :
     161                EnumSet.of(flags[0], flags);
     162        this.zip = flagSet.contains(SessionWriterFlags.IS_ZIP);
     163        this.plugins = flagSet.contains(SessionWriterFlags.SAVE_PLUGIN_INFORMATION);
    128164    }
    129165
     
    219255     */
    220256    public Document createJosDocument() throws IOException {
    221         DocumentBuilder builder = null;
     257        DocumentBuilder builder;
    222258        try {
    223259            builder = XmlUtils.newSafeDOMBuilder();
     
    362398            zipOut.putNextEntry(entry);
    363399            writeJos(doc, zipOut);
     400            if (this.plugins) {
     401                for (PluginSessionExporter exporter : PluginHandler.load(PluginSessionExporter.class)) {
     402                    exporter.writeZipEntries(zipOut);
     403                }
     404            }
    364405            Utils.close(zipOut);
    365406        } else {
  • trunk/src/org/openstreetmap/josm/plugins/PluginHandler.java

    r18801 r18833  
    3333import java.util.Map.Entry;
    3434import java.util.Objects;
     35import java.util.ServiceLoader;
    3536import java.util.Set;
    3637import java.util.TreeMap;
     
    359360
    360361    /**
     362     * Get a {@link ServiceLoader} for the specified service. This uses {@link #getJoinedPluginResourceCL()} as the
     363     * class loader, so that we don't have to iterate through the {@link ClassLoader}s from {@link #getPluginClassLoaders()}.
     364     * @param <S> The service type
     365     * @param service The service class to look for
     366     * @return The service loader
     367     * @since 18833
     368     */
     369    public static <S> ServiceLoader<S> load(Class<S> service) {
     370        return ServiceLoader.load(service, getJoinedPluginResourceCL());
     371    }
     372
     373    /**
    361374     * Removes deprecated plugins from a collection of plugins. Modifies the
    362375     * collection <code>plugins</code>.
    363      *
     376     * <p>
    364377     * Also notifies the user about removed deprecated plugins
    365378     *
     
    412425     * collection <code>plugins</code>. Also removes the plugin from the list
    413426     * of plugins in the preferences, if necessary.
    414      *
     427     * <p>
    415428     * Asks the user for every unmaintained plugin whether it should be removed.
    416429     * @param parent The parent Component used to display warning popup
     
    778791    /**
    779792     * Get class loader to locate resources from plugins.
    780      *
     793     * <p>
    781794     * It joins URLs of all plugins, to find images, etc.
    782795     * (Not for loading Java classes - each plugin has a separate {@link PluginClassLoader}
     
    931944     * Loads plugins from <code>plugins</code> which have the flag {@link PluginInformation#early} set to true
    932945     * <i>and</i> a negative {@link PluginInformation#stage} value.
    933      *
     946     * <p>
    934947     * This is meant for plugins that provide additional {@link javax.swing.LookAndFeel}.
    935948     */
     
    10411054        try {
    10421055            monitor.beginTask(tr("Determining plugins to load..."));
    1043             Set<String> plugins = new HashSet<>(Config.getPref().getList("plugins", new LinkedList<String>()));
     1056            Set<String> plugins = new HashSet<>(Config.getPref().getList("plugins", new LinkedList<>()));
    10441057            Logging.debug("Plugins list initialized to {0}", plugins);
    10451058            String systemProp = Utils.getSystemProperty("josm.plugins");
     
    13341347    /**
    13351348     * Installs downloaded plugins. Moves files with the suffix ".jar.new" to the corresponding ".jar" files.
    1336      *
     1349     * <p>
    13371350     * If {@code dowarn} is true, this methods emits warning messages on the console if a downloaded
    13381351     * but not yet installed plugin .jar can't be be installed. If {@code dowarn} is false, the
  • trunk/test/unit/org/openstreetmap/josm/testutils/FakeOsmApi.java

    r10373 r18833  
    110110    /**
    111111     * Gets and caches an instance of this API.
    112      * @return The API intance. Always the same object.
     112     * @return The API instance. Always the same object.
    113113     */
    114114    public static synchronized FakeOsmApi getInstance() {
    115115        if (instance == null) {
    116116            instance = new FakeOsmApi();
    117             cacheInstance(instance);
    118117        }
     118        cacheInstance(instance);
    119119        return instance;
    120120    }
Note: See TracChangeset for help on using the changeset viewer.