Ticket #17052: 17052.core.patch

File 17052.core.patch, 24.4 KB (added by taylor.smock, 3 years ago)

Core changes

  • src/org/openstreetmap/josm/actions/SessionLoadAction.java

    Subject: [PATCH] ImportExportPlugins
    ---
    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/actions/SessionLoadAction.java b/src/org/openstreetmap/josm/actions/SessionLoadAction.java
    a b  
    1212import java.nio.file.Files;
    1313import java.nio.file.StandardCopyOption;
    1414import java.util.Arrays;
     15import java.util.EnumSet;
    1516import java.util.List;
    1617
    1718import javax.swing.JFileChooser;
     
    3334import org.openstreetmap.josm.io.session.SessionReader;
    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;
    3840import org.openstreetmap.josm.tools.Logging;
     
    205207                    postLoadTasks = reader.getPostLoadTasks();
    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) {
    211220                        Utils.deleteFile(file);
  • src/org/openstreetmap/josm/actions/SessionSaveAction.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/actions/SessionSaveAction.java b/src/org/openstreetmap/josm/actions/SessionSaveAction.java
    a b  
    1717import java.util.ArrayList;
    1818import java.util.Arrays;
    1919import java.util.Collection;
     20import java.util.EnumSet;
    2021import java.util.HashMap;
    2122import java.util.HashSet;
    2223import java.util.List;
     
    5657import org.openstreetmap.josm.gui.util.GuiHelper;
    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;
    6366import org.openstreetmap.josm.tools.JosmRuntimeException;
     
    7881    private transient MultiMap<Layer, Layer> dependencies;
    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
    8387    protected transient FileFilter joz = new ExtensionFileFilter("joz", "joz", tr("Session file (archive) (*.joz)"));
     
    8892    private static String tooltip = TOOLTIP_DEFAULT;
    8993    static File sessionFile;
    9094    static boolean isZipSessionFile;
     95    private static boolean pluginData;
    9196    static List<WeakReference<Layer>> layersInSessionFile;
    9297
    9398    private static final SessionSaveAction instance = new SessionSaveAction();
     
    170175                .collect(Collectors.toList());
    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);
    176181
     
    240245            active = layersOut.indexOf(activeLayer);
    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());
    246258            sw.write(sessionFile);
     
    434446            op.add(tabs, GBC.eol().fill());
    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        }
    440458
     
    509527     * @param file file
    510528     * @param zip if it is a zip session file
    511529     * @param layers layers that are currently represented in the session file
     530     * @deprecated since xxx, use {@link #setCurrentSession(File, List, SessionWriter.SessionWriterFlags...)} instead
    512531     */
     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 xxx
     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 xxx
     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
    518567    /**
     
    550599        return tooltip;
    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}
  • src/org/openstreetmap/josm/io/session/GenericSessionExporter.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/session/GenericSessionExporter.java b/src/org/openstreetmap/josm/io/session/GenericSessionExporter.java
    a b  
    3131import org.openstreetmap.josm.gui.util.GuiHelper;
    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;
    3637import org.w3c.dom.Element;
     
    210211
    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
    216225    protected abstract void addDataFile(OutputStream out) throws IOException;
  • new file src/org/openstreetmap/josm/io/session/PluginSessionExporter.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/session/PluginSessionExporter.java b/src/org/openstreetmap/josm/io/session/PluginSessionExporter.java
    new file mode 100644
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.io.session;
     3
     4import java.io.IOException;
     5import java.io.OutputStream;
     6import java.util.zip.ZipEntry;
     7import java.util.zip.ZipException;
     8import java.util.zip.ZipOutputStream;
     9
     10/**
     11 * Export arbitrary data from a plugin.
     12 * @since xxx
     13 */
     14public interface PluginSessionExporter {
     15    /**
     16     * Get the filename to store the data in the archive
     17     * @return The filename
     18     * @see PluginSessionImporter#getFileName()
     19     */
     20    String getFileName();
     21
     22    /**
     23     * Check to see if the specified exporter needs to save anything
     24     * @return {@code true} if the exporter needs to save something
     25     */
     26    boolean requiresSaving();
     27
     28    /**
     29     * Write data to a zip file
     30     * @param zipOut The zip output stream
     31     * @throws IOException see {@link ZipOutputStream#putNextEntry(ZipEntry)}
     32     * @throws ZipException see {@link ZipOutputStream#putNextEntry(ZipEntry)}
     33     */
     34    default void writeZipEntries(ZipOutputStream zipOut) throws IOException {
     35        if (requiresSaving()) {
     36            final ZipEntry zipEntry = new ZipEntry(this.getFileName());
     37            zipOut.putNextEntry(zipEntry);
     38            this.write(zipOut);
     39        }
     40    }
     41
     42    /**
     43     * Write the plugin data to a stream
     44     * @param outputStream The stream to write to
     45     */
     46    void write(OutputStream outputStream);
     47}
  • new file src/org/openstreetmap/josm/io/session/PluginSessionImporter.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/session/PluginSessionImporter.java b/src/org/openstreetmap/josm/io/session/PluginSessionImporter.java
    new file mode 100644
    - +  
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.io.session;
     3
     4import java.io.IOException;
     5import java.io.InputStream;
     6import java.util.zip.ZipEntry;
     7import java.util.zip.ZipFile;
     8
     9/**
     10 * Import arbitrary data for a plugin.
     11 * @since xxx
     12 */
     13public interface PluginSessionImporter {
     14    /**
     15     * Get the filename that was used to store data in the archive.
     16     * @return The filename
     17     * @see PluginSessionExporter#getFileName()
     18     */
     19    String getFileName();
     20
     21    /**
     22     * Read data from a file stream
     23     * @param inputStream The stream to read
     24     * @return {@code true} if the importer loaded data
     25     */
     26    boolean read(InputStream inputStream);
     27
     28    /**
     29     * Read the data from a zip file
     30     * @param zipFile The zipfile to read
     31     * @return {@code true} if the importer loaded data
     32     * @throws IOException if there was an issue reading the zip file. See {@link ZipFile#getInputStream(ZipEntry)}.
     33     */
     34    default boolean readZipFile(ZipFile zipFile) throws IOException {
     35        final ZipEntry entry = zipFile.getEntry(this.getFileName());
     36        if (entry != null) {
     37            try (InputStream inputStream = zipFile.getInputStream(entry)) {
     38                return this.read(inputStream);
     39            }
     40        }
     41        return false;
     42    }
     43}
  • src/org/openstreetmap/josm/io/session/SessionReader.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/session/SessionReader.java b/src/org/openstreetmap/josm/io/session/SessionReader.java
    a b  
    3939import org.openstreetmap.josm.data.coor.ILatLon;
    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;
    4445import org.openstreetmap.josm.gui.layer.Layer;
    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;
    5154import org.openstreetmap.josm.tools.Logging;
     
    156159
    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<>();
    161165    private int active = -1;
     
    192196        Class<? extends SessionLayerImporter> importerClass = sessionLayerImporters.get(layerType);
    193197        if (importerClass == null)
    194198            return null;
    195         SessionLayerImporter importer = null;
     199        SessionLayerImporter importer;
    196200        try {
    197201            importer = importerClass.getConstructor().newInstance();
    198202        } catch (ReflectiveOperationException e) {
     
    243247        return projectionChoice;
    244248    }
    245249
     250    /**
     251     * Returns whether plugins loaded additonal data
     252     * @return {@code true} if at least one plugin loaded additional data
     253     * @since xxx
     254     */
     255    public boolean loadedPluginData() {
     256        return this.pluginData;
     257    }
     258
    246259    /**
    247260     * A class that provides some context for the individual {@link SessionLayerImporter}
    248261     * when doing the import.
     
    308321
    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"
    316329         *         "file:/home/user/data.osm"
     
    351364
    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.
    357370         * @param uriStr the URI as string
     
    712725    /**
    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.
    718731     */
     
    742755        }
    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.
    747773     * @param sessionFile session file to load
     
    753779    public void loadSession(File sessionFile, boolean zip, ProgressMonitor progressMonitor) throws IllegalDataException, IOException {
    754780        try (InputStream josIS = createInputStream(sessionFile, zip)) {
    755781            loadSession(josIS, sessionFile.toURI(), zip, progressMonitor);
     782            this.postLoadTasks.add(this::loadPluginData);
    756783        }
    757784    }
    758785
  • src/org/openstreetmap/josm/io/session/SessionWriter.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/io/session/SessionWriter.java b/src/org/openstreetmap/josm/io/session/SessionWriter.java
    a b  
    99import java.nio.charset.StandardCharsets;
    1010import java.nio.file.Files;
    1111import java.util.Collection;
     12import java.util.EnumSet;
    1213import java.util.HashMap;
    1314import java.util.List;
    1415import java.util.Locale;
     
    4344import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
    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;
    4850import org.openstreetmap.josm.tools.MultiMap;
     
    5860 */
    5961public class SessionWriter {
    6062
     63    /**
     64     * {@link SessionWriter} options
     65     * @since xxx
     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
    6380    private final List<Layer> layers;
     
    6582    private final Map<Layer, SessionLayerExporter> exporters;
    6683    private final MultiMap<Layer, Layer> dependencies;
    6784    private final boolean zip;
     85    private final boolean plugins;
    6886
    6987    private ZipOutputStream zipOut;
    7088
     
    82100
    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
    88106     * @param exporter exporter for this layer class
     
    120138     */
    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 xxx
     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
    130166    /**
     
    218254     * @throws IOException if any I/O error occurs
    219255     */
    220256    public Document createJosDocument() throws IOException {
    221         DocumentBuilder builder = null;
     257        DocumentBuilder builder;
    222258        try {
    223259            builder = XmlUtils.newSafeDOMBuilder();
    224260        } catch (ParserConfigurationException e) {
     
    361397            ZipEntry entry = new ZipEntry("session.jos");
    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 {
    366407            writeJos(doc, new BufferedOutputStream(out));
  • src/org/openstreetmap/josm/plugins/PluginHandler.java

    IDEA additional info:
    Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
    <+>UTF-8
    diff --git a/src/org/openstreetmap/josm/plugins/PluginHandler.java b/src/org/openstreetmap/josm/plugins/PluginHandler.java
    a b  
    3232import java.util.Map;
    3333import java.util.Map.Entry;
    3434import java.util.Objects;
     35import java.util.ServiceLoader;
    3536import java.util.Set;
    3637import java.util.TreeMap;
    3738import java.util.TreeSet;
     
    357358        return Collections.unmodifiableCollection(classLoaders.values());
    358359    }
    359360
     361    /**
     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 xxx
     368     */
     369    public static <S> ServiceLoader<S> load(Class<S> service) {
     370        return ServiceLoader.load(service, getJoinedPluginResourceCL());
     371    }
     372
    360373    /**
    361374     * Removes deprecated plugins from a collection of plugins. Modifies the
    362375     * collection <code>plugins</code>.