Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 18034)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayer.java	(revision 18035)
@@ -20,13 +20,8 @@
 import java.awt.image.BufferedImage;
 import java.io.File;
-import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashSet;
-import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -34,5 +29,4 @@
 import javax.swing.Action;
 import javax.swing.Icon;
-import javax.swing.JOptionPane;
 
 import org.openstreetmap.josm.actions.AutoScaleAction;
@@ -53,8 +47,6 @@
 import org.openstreetmap.josm.gui.MapView;
 import org.openstreetmap.josm.gui.NavigatableComponent;
-import org.openstreetmap.josm.gui.PleaseWaitRunnable;
 import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
 import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
-import org.openstreetmap.josm.gui.io.importexport.ImageImporter;
 import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
 import org.openstreetmap.josm.gui.layer.GpxLayer;
@@ -65,5 +57,4 @@
 import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
 import org.openstreetmap.josm.tools.ImageProvider;
-import org.openstreetmap.josm.tools.Logging;
 import org.openstreetmap.josm.tools.Utils;
 
@@ -225,156 +216,4 @@
 
     /**
-     * Loads a set of images, while displaying a dialog that indicates what the plugin is currently doing.
-     * In facts, this object is instantiated with a list of files. These files may be JPEG files or
-     * directories. In case of directories, they are scanned to find all the images they contain.
-     * Then all the images that have be found are loaded as ImageEntry instances.
-     */
-    static final class Loader extends PleaseWaitRunnable {
-
-        private boolean canceled;
-        private GeoImageLayer layer;
-        private final Collection<File> selection;
-        private final Set<String> loadedDirectories = new HashSet<>();
-        private final Set<String> errorMessages;
-        private final GpxLayer gpxLayer;
-
-        Loader(Collection<File> selection, GpxLayer gpxLayer) {
-            super(tr("Extracting GPS locations from EXIF"));
-            this.selection = selection;
-            this.gpxLayer = gpxLayer;
-            errorMessages = new LinkedHashSet<>();
-        }
-
-        private void rememberError(String message) {
-            this.errorMessages.add(message);
-        }
-
-        @Override
-        protected void realRun() throws IOException {
-
-            progressMonitor.subTask(tr("Starting directory scan"));
-            Collection<File> files = new ArrayList<>();
-            try {
-                addRecursiveFiles(files, selection);
-            } catch (IllegalStateException e) {
-                Logging.debug(e);
-                rememberError(e.getMessage());
-            }
-
-            if (canceled)
-                return;
-            progressMonitor.subTask(tr("Read photos..."));
-            progressMonitor.setTicksCount(files.size());
-
-            // read the image files
-            List<ImageEntry> entries = new ArrayList<>(files.size());
-
-            for (File f : files) {
-
-                if (canceled) {
-                    break;
-                }
-
-                progressMonitor.subTask(tr("Reading {0}...", f.getName()));
-                progressMonitor.worked(1);
-
-                ImageEntry e = new ImageEntry(f);
-                e.extractExif();
-                entries.add(e);
-            }
-            layer = new GeoImageLayer(entries, gpxLayer);
-            files.clear();
-        }
-
-        private void addRecursiveFiles(Collection<File> files, Collection<File> sel) {
-            boolean nullFile = false;
-
-            for (File f : sel) {
-
-                if (canceled) {
-                    break;
-                }
-
-                if (f == null) {
-                    nullFile = true;
-
-                } else if (f.isDirectory()) {
-                    String canonical = null;
-                    try {
-                        canonical = f.getCanonicalPath();
-                    } catch (IOException e) {
-                        Logging.error(e);
-                        rememberError(tr("Unable to get canonical path for directory {0}\n",
-                                f.getAbsolutePath()));
-                    }
-
-                    if (canonical == null || loadedDirectories.contains(canonical)) {
-                        continue;
-                    } else {
-                        loadedDirectories.add(canonical);
-                    }
-
-                    File[] children = f.listFiles(ImageImporter.FILE_FILTER_WITH_FOLDERS);
-                    if (children != null) {
-                        progressMonitor.subTask(tr("Scanning directory {0}", f.getPath()));
-                        addRecursiveFiles(files, Arrays.asList(children));
-                    } else {
-                        rememberError(tr("Error while getting files from directory {0}\n", f.getPath()));
-                    }
-
-                } else {
-                    files.add(f);
-                }
-            }
-
-            if (nullFile) {
-                throw new IllegalStateException(tr("One of the selected files was null"));
-            }
-        }
-
-        private String formatErrorMessages() {
-            StringBuilder sb = new StringBuilder();
-            sb.append("<html>");
-            if (errorMessages.size() == 1) {
-                sb.append(Utils.escapeReservedCharactersHTML(errorMessages.iterator().next()));
-            } else {
-                sb.append(Utils.joinAsHtmlUnorderedList(errorMessages));
-            }
-            sb.append("</html>");
-            return sb.toString();
-        }
-
-        @Override protected void finish() {
-            if (!errorMessages.isEmpty()) {
-                JOptionPane.showMessageDialog(
-                        MainApplication.getMainFrame(),
-                        formatErrorMessages(),
-                        tr("Error"),
-                        JOptionPane.ERROR_MESSAGE
-                        );
-            }
-            if (layer != null) {
-                MainApplication.getLayerManager().addLayer(layer);
-
-                if (!canceled && !layer.getImageData().getImages().isEmpty()) {
-                    boolean noGeotagFound = true;
-                    for (ImageEntry e : layer.getImageData().getImages()) {
-                        if (e.getPos() != null) {
-                            noGeotagFound = false;
-                        }
-                    }
-                    if (noGeotagFound) {
-                        new CorrelateGpxWithImages(layer).actionPerformed(null);
-                    }
-                }
-            }
-        }
-
-        @Override protected void cancel() {
-            canceled = true;
-        }
-    }
-
-    /**
      * Create a GeoImageLayer asynchronously
      * @param files the list of image files to display
@@ -382,5 +221,5 @@
      */
     public static void create(Collection<File> files, GpxLayer gpxLayer) {
-        MainApplication.worker.execute(new Loader(files, gpxLayer));
+        MainApplication.worker.execute(new ImagesLoader(files, gpxLayer));
     }
 
@@ -400,5 +239,4 @@
     @Override
     public Action[] getMenuEntries() {
-
         List<Action> entries = new ArrayList<>();
         entries.add(LayerListDialog.getInstance().createShowHideLayerAction());
@@ -421,5 +259,4 @@
 
         return entries.toArray(new Action[0]);
-
     }
 
@@ -447,5 +284,6 @@
     }
 
-    @Override public Object getInfoComponent() {
+    @Override
+    public Object getInfoComponent() {
         return infoText();
     }
@@ -549,8 +387,8 @@
             }
 
-            if (null == offscreenBuffer || offscreenBuffer.getWidth() != width  // reuse the old buffer if possible
+            if (null == offscreenBuffer
+                    || offscreenBuffer.getWidth() != width  // reuse the old buffer if possible
                     || offscreenBuffer.getHeight() != height) {
-                offscreenBuffer = new BufferedImage(width, height,
-                        BufferedImage.TYPE_INT_ARGB);
+                offscreenBuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
                 updateOffscreenBuffer = true;
             }
@@ -970,8 +808,6 @@
         if (gpxFauxLayer == null) {
             GpxData gpxData = new GpxData();
-            List<ImageEntry> imageList = data.getImages();
-            for (ImageEntry image : imageList) {
-                WayPoint twaypoint = new WayPoint(image.getPos());
-                gpxData.addWaypoint(twaypoint);
+            for (ImageEntry image : data.getImages()) {
+                gpxData.addWaypoint(new WayPoint(image.getPos()));
             }
             gpxFauxLayer = new GpxLayer(gpxData);
Index: /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImagesLoader.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImagesLoader.java	(revision 18035)
+++ /trunk/src/org/openstreetmap/josm/gui/layer/geoimage/ImagesLoader.java	(revision 18035)
@@ -0,0 +1,183 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.layer.geoimage;
+
+import static org.openstreetmap.josm.tools.I18n.tr;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.JOptionPane;
+
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.PleaseWaitRunnable;
+import org.openstreetmap.josm.gui.io.importexport.ImageImporter;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.tools.Logging;
+import org.openstreetmap.josm.tools.Utils;
+
+/**
+ * Loads a set of images, while displaying a dialog that indicates what the plugin is currently doing.
+ * In facts, this object is instantiated with a list of files. These files may be JPEG files or
+ * directories. In case of directories, they are scanned to find all the images they contain.
+ * Then all the images that have be found are loaded as ImageEntry instances.
+ *
+ * @since 18035 (extracted from GeoImageLayer)
+ */
+final class ImagesLoader extends PleaseWaitRunnable {
+
+    private boolean canceled;
+    private GeoImageLayer layer;
+    private final Collection<File> selection;
+    private final Set<String> loadedDirectories = new HashSet<>();
+    private final Set<String> errorMessages;
+    private final GpxLayer gpxLayer;
+
+    /**
+     * Constructs a new {@code ImagesLoader}.
+     * @param selection
+     * @param gpxLayer
+     */
+    public ImagesLoader(Collection<File> selection, GpxLayer gpxLayer) {
+        super(tr("Extracting GPS locations from EXIF"));
+        this.selection = selection;
+        this.gpxLayer = gpxLayer;
+        errorMessages = new LinkedHashSet<>();
+    }
+
+    private void rememberError(String message) {
+        this.errorMessages.add(message);
+    }
+
+    @Override
+    protected void realRun() throws IOException {
+        progressMonitor.subTask(tr("Starting directory scan"));
+        Collection<File> files = new ArrayList<>();
+        try {
+            addRecursiveFiles(files, selection);
+        } catch (IllegalStateException e) {
+            Logging.debug(e);
+            rememberError(e.getMessage());
+        }
+
+        if (canceled)
+            return;
+        progressMonitor.subTask(tr("Read photos..."));
+        progressMonitor.setTicksCount(files.size());
+
+        // read the image files
+        List<ImageEntry> entries = new ArrayList<>(files.size());
+
+        for (File f : files) {
+
+            if (canceled) {
+                break;
+            }
+
+            progressMonitor.subTask(tr("Reading {0}...", f.getName()));
+            progressMonitor.worked(1);
+
+            ImageEntry e = new ImageEntry(f);
+            e.extractExif();
+            entries.add(e);
+        }
+        layer = new GeoImageLayer(entries, gpxLayer);
+        files.clear();
+    }
+
+    private void addRecursiveFiles(Collection<File> files, Collection<File> sel) {
+        boolean nullFile = false;
+
+        for (File f : sel) {
+
+            if (canceled) {
+                break;
+            }
+
+            if (f == null) {
+                nullFile = true;
+
+            } else if (f.isDirectory()) {
+                String canonical = null;
+                try {
+                    canonical = f.getCanonicalPath();
+                } catch (IOException e) {
+                    Logging.error(e);
+                    rememberError(tr("Unable to get canonical path for directory {0}\n",
+                            f.getAbsolutePath()));
+                }
+
+                if (canonical == null || loadedDirectories.contains(canonical)) {
+                    continue;
+                } else {
+                    loadedDirectories.add(canonical);
+                }
+
+                File[] children = f.listFiles(ImageImporter.FILE_FILTER_WITH_FOLDERS);
+                if (children != null) {
+                    progressMonitor.subTask(tr("Scanning directory {0}", f.getPath()));
+                    addRecursiveFiles(files, Arrays.asList(children));
+                } else {
+                    rememberError(tr("Error while getting files from directory {0}\n", f.getPath()));
+                }
+
+            } else {
+                files.add(f);
+            }
+        }
+
+        if (nullFile) {
+            throw new IllegalStateException(tr("One of the selected files was null"));
+        }
+    }
+
+    private String formatErrorMessages() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("<html>");
+        if (errorMessages.size() == 1) {
+            sb.append(Utils.escapeReservedCharactersHTML(errorMessages.iterator().next()));
+        } else {
+            sb.append(Utils.joinAsHtmlUnorderedList(errorMessages));
+        }
+        sb.append("</html>");
+        return sb.toString();
+    }
+
+    @Override
+    protected void finish() {
+        if (!errorMessages.isEmpty()) {
+            JOptionPane.showMessageDialog(
+                    MainApplication.getMainFrame(),
+                    formatErrorMessages(),
+                    tr("Error"),
+                    JOptionPane.ERROR_MESSAGE
+                    );
+        }
+        if (layer != null) {
+            MainApplication.getLayerManager().addLayer(layer);
+
+            if (!canceled && !layer.getImageData().getImages().isEmpty()) {
+                boolean noGeotagFound = true;
+                for (ImageEntry e : layer.getImageData().getImages()) {
+                    if (e.getPos() != null) {
+                        noGeotagFound = false;
+                    }
+                }
+                if (noGeotagFound) {
+                    new CorrelateGpxWithImages(layer).actionPerformed(null);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void cancel() {
+        canceled = true;
+    }
+}
Index: /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayerTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayerTest.java	(revision 18034)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/GeoImageLayerTest.java	(revision 18035)
@@ -2,22 +2,10 @@
 package org.openstreetmap.josm.gui.layer.geoimage;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.junit.jupiter.api.Assertions.assertThrows;
-
-import java.io.File;
-import java.io.InputStream;
-import java.util.Collections;
-import java.util.List;
 
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
-import org.openstreetmap.josm.TestUtils;
 import org.openstreetmap.josm.data.osm.DataSet;
-import org.openstreetmap.josm.gui.MainApplication;
-import org.openstreetmap.josm.gui.layer.GpxLayer;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
-import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer.Loader;
-import org.openstreetmap.josm.io.GpxReader;
 import org.openstreetmap.josm.testutils.JOSMTestRules;
 
@@ -36,29 +24,4 @@
 
     /**
-     * Unit test of {@link Loader} class.
-     * @throws Exception if any error occurs
-     */
-    @Test
-    void testLoader() throws Exception {
-        try (InputStream in = TestUtils.getRegressionDataStream(12255, "bobrava2.gpx")) {
-            GpxReader reader = new GpxReader(in);
-            assertTrue(reader.parse(true));
-            GpxLayer gpxLayer = new GpxLayer(reader.getGpxData());
-            MainApplication.getLayerManager().addLayer(gpxLayer);
-            assertEquals(1, MainApplication.getLayerManager().getLayers().size());
-            new Loader(
-                    Collections.singleton(new File(TestUtils.getRegressionDataFile(12255, "G0016941.JPG"))),
-                    gpxLayer).run();
-            assertEquals(2, MainApplication.getLayerManager().getLayers().size());
-            GeoImageLayer layer = MainApplication.getLayerManager().getLayersOfType(GeoImageLayer.class).iterator().next();
-            assertEquals(gpxLayer, layer.getGpxLayer());
-            List<ImageEntry> images = layer.getImages();
-            assertEquals(1, images.size());
-            assertEquals("<html>1 image loaded. 0 were found to be GPS tagged.</html>", layer.getInfoComponent());
-            assertEquals("<html>1 image loaded. 0 were found to be GPS tagged.</html>", layer.getToolTipText());
-        }
-    }
-
-    /**
      * Test that {@link GeoImageLayer#mergeFrom} throws IAE for invalid arguments
      */
Index: /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/ImagesLoaderTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/ImagesLoaderTest.java	(revision 18035)
+++ /trunk/test/unit/org/openstreetmap/josm/gui/layer/geoimage/ImagesLoaderTest.java	(revision 18035)
@@ -0,0 +1,58 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.layer.geoimage;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.openstreetmap.josm.TestUtils;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.io.GpxReader;
+import org.openstreetmap.josm.testutils.JOSMTestRules;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * Unit tests of {@link ImagesLoader} class.
+ */
+class ImagesLoaderTest {
+
+    /**
+     * We need prefs for this.
+     */
+    @RegisterExtension
+    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
+    public JOSMTestRules test = new JOSMTestRules().preferences();
+
+    /**
+     * Unit test of {@link ImagesLoader} class.
+     * @throws Exception if any error occurs
+     */
+    @Test
+    void testLoader() throws Exception {
+        try (InputStream in = TestUtils.getRegressionDataStream(12255, "bobrava2.gpx")) {
+            GpxReader reader = new GpxReader(in);
+            assertTrue(reader.parse(true));
+            GpxLayer gpxLayer = new GpxLayer(reader.getGpxData());
+            MainApplication.getLayerManager().addLayer(gpxLayer);
+            assertEquals(1, MainApplication.getLayerManager().getLayers().size());
+            new ImagesLoader(
+                    Collections.singleton(new File(TestUtils.getRegressionDataFile(12255, "G0016941.JPG"))),
+                    gpxLayer).run();
+            assertEquals(2, MainApplication.getLayerManager().getLayers().size());
+            GeoImageLayer layer = MainApplication.getLayerManager().getLayersOfType(GeoImageLayer.class).iterator().next();
+            assertEquals(gpxLayer, layer.getGpxLayer());
+            List<ImageEntry> images = layer.getImages();
+            assertEquals(1, images.size());
+            assertEquals("<html>1 image loaded. 0 were found to be GPS tagged.</html>", layer.getInfoComponent());
+            assertEquals("<html>1 image loaded. 0 were found to be GPS tagged.</html>", layer.getToolTipText());
+        }
+    }
+}
