Index: /trunk/src/org/openstreetmap/josm/actions/ExtensionFileFilter.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/ExtensionFileFilter.java	(revision 10298)
+++ /trunk/src/org/openstreetmap/josm/actions/ExtensionFileFilter.java	(revision 10299)
@@ -319,5 +319,5 @@
             defaultExtension,
             description + (!extensionsForDescription.isEmpty()
-                ? (" (" + Utils.join(", ", extensionsForDescription) + ")")
+                ? (" (" + Utils.join(", ", extensionsForDescription) + ')')
                 : "")
             );
Index: /trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java	(revision 10298)
+++ /trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java	(revision 10299)
@@ -244,5 +244,5 @@
 
     public interface BinaryMatchFactory extends MatchFactory {
-        BinaryMatch get(String keyword, Match lhs, Match rhs, PushbackTokenizer tokenizer) throws ParseError;
+        AbstractBinaryMatch get(String keyword, Match lhs, Match rhs, PushbackTokenizer tokenizer) throws ParseError;
     }
 
@@ -337,19 +337,32 @@
      * A binary search operator which may take data parameters.
      */
-    public abstract static class BinaryMatch extends Match {
+    public abstract static class AbstractBinaryMatch extends Match {
 
         protected final Match lhs;
         protected final Match rhs;
 
-        public BinaryMatch(Match lhs, Match rhs) {
+        /**
+         * Constructs a new {@code BinaryMatch}.
+         * @param lhs Left hand side
+         * @param rhs Right hand side
+         */
+        public AbstractBinaryMatch(Match lhs, Match rhs) {
             this.lhs = lhs;
             this.rhs = rhs;
         }
 
-        public Match getLhs() {
+        /**
+         * Returns left hand side.
+         * @return left hand side
+         */
+        public final Match getLhs() {
             return lhs;
         }
 
-        public Match getRhs() {
+        /**
+         * Returns right hand side.
+         * @return right hand side
+         */
+        public final Match getRhs() {
             return rhs;
         }
@@ -438,5 +451,10 @@
      * Matches if both left and right expressions match.
      */
-    public static class And extends BinaryMatch {
+    public static class And extends AbstractBinaryMatch {
+        /**
+         * Constructs a new {@code And} match.
+         * @param lhs left hand side
+         * @param rhs right hand side
+         */
         public And(Match lhs, Match rhs) {
             super(lhs, rhs);
@@ -455,6 +473,6 @@
         @Override
         public String toString() {
-            return (lhs instanceof BinaryMatch && !(lhs instanceof And) ? "(" + lhs + ")" : lhs) + " && "
-                    + (rhs instanceof BinaryMatch && !(rhs instanceof And) ? "(" + rhs + ")" : rhs);
+            return (lhs instanceof AbstractBinaryMatch && !(lhs instanceof And) ? ("(" + lhs + ')') : lhs) + " && "
+                    + (rhs instanceof AbstractBinaryMatch && !(rhs instanceof And) ? ("(" + rhs + ')') : rhs);
         }
     }
@@ -463,5 +481,10 @@
      * Matches if the left OR the right expression match.
      */
-    public static class Or extends BinaryMatch {
+    public static class Or extends AbstractBinaryMatch {
+        /**
+         * Constructs a new {@code Or} match.
+         * @param lhs left hand side
+         * @param rhs right hand side
+         */
         public Or(Match lhs, Match rhs) {
             super(lhs, rhs);
@@ -480,6 +503,6 @@
         @Override
         public String toString() {
-            return (lhs instanceof BinaryMatch && !(lhs instanceof Or) ? "(" + lhs + ")" : lhs) + " || "
-                    + (rhs instanceof BinaryMatch && !(rhs instanceof Or) ? "(" + rhs + ")" : rhs);
+            return (lhs instanceof AbstractBinaryMatch && !(lhs instanceof Or) ? ("(" + lhs + ')') : lhs) + " || "
+                    + (rhs instanceof AbstractBinaryMatch && !(rhs instanceof Or) ? ("(" + rhs + ')') : rhs);
         }
     }
@@ -488,5 +511,10 @@
      * Matches if the left OR the right expression match, but not both.
      */
-    public static class Xor extends BinaryMatch {
+    public static class Xor extends AbstractBinaryMatch {
+        /**
+         * Constructs a new {@code Xor} match.
+         * @param lhs left hand side
+         * @param rhs right hand side
+         */
         public Xor(Match lhs, Match rhs) {
             super(lhs, rhs);
@@ -505,6 +533,6 @@
         @Override
         public String toString() {
-            return (lhs instanceof BinaryMatch && !(lhs instanceof Xor) ? "(" + lhs + ")" : lhs) + " ^ "
-                    + (rhs instanceof BinaryMatch && !(rhs instanceof Xor) ? "(" + rhs + ")" : rhs);
+            return (lhs instanceof AbstractBinaryMatch && !(lhs instanceof Xor) ? ("(" + lhs + ')') : lhs) + " ^ "
+                    + (rhs instanceof AbstractBinaryMatch && !(rhs instanceof Xor) ? ("(" + rhs + ')') : rhs);
         }
     }
@@ -1755,5 +1783,5 @@
         final String forKey = '"' + escapeStringForSearch(key) + '"' + '=';
         if (value == null || value.isEmpty()) {
-            return forKey + "*";
+            return forKey + '*';
         } else {
             return forKey + '"' + escapeStringForSearch(value) + '"';
Index: /trunk/src/org/openstreetmap/josm/data/AutosaveTask.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/AutosaveTask.java	(revision 10298)
+++ /trunk/src/org/openstreetmap/josm/data/AutosaveTask.java	(revision 10299)
@@ -13,4 +13,5 @@
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Date;
@@ -73,9 +74,13 @@
     public static final BooleanProperty PROP_NOTIFICATION = new BooleanProperty("autosave.notification", false);
 
-    private static class AutosaveLayerInfo {
-        private OsmDataLayer layer;
+    protected static final class AutosaveLayerInfo {
+        private final OsmDataLayer layer;
         private String layerName;
         private String layerFileName;
         private final Deque<File> backupFiles = new LinkedList<>();
+
+        AutosaveLayerInfo(OsmDataLayer layer) {
+            this.layer = layer;
+        }
     }
 
@@ -89,4 +94,13 @@
     private final File autosaveDir = new File(Main.pref.getUserDataDirectory(), AUTOSAVE_DIR);
     private final File deletedLayersDir = new File(Main.pref.getUserDataDirectory(), DELETED_LAYERS_DIR);
+
+    /**
+     * Replies the autosave directory.
+     * @return the autosave directory
+     * @since 10299
+     */
+    public final Path getAutosaveDir() {
+        return autosaveDir.toPath();
+    }
 
     public void schedule() {
@@ -153,24 +167,18 @@
     }
 
-    private File getNewLayerFile(AutosaveLayerInfo layer) {
-        int index = 0;
-        Date now = new Date();
+    protected File getNewLayerFile(AutosaveLayerInfo layer, Date now, int startIndex) {
+        int index = startIndex;
         while (true) {
             String filename = String.format("%1$s_%2$tY%2$tm%2$td_%2$tH%2$tM%2$tS%2$tL%3$s",
-                    layer.layerFileName, now, index == 0 ? "" : '_' + index);
-            File result = new File(autosaveDir, filename + "." + Main.pref.get("autosave.extension", "osm"));
+                    layer.layerFileName, now, index == 0 ? "" : ("_" + index));
+            File result = new File(autosaveDir, filename + '.' + Main.pref.get("autosave.extension", "osm"));
             try {
+                if (index > PROP_INDEX_LIMIT.get())
+                    throw new IOException("index limit exceeded");
                 if (result.createNewFile()) {
-                    File pidFile = new File(autosaveDir, filename+".pid");
-                    try (PrintStream ps = new PrintStream(pidFile, "UTF-8")) {
-                        ps.println(ManagementFactory.getRuntimeMXBean().getName());
-                    } catch (IOException | SecurityException t) {
-                        Main.error(t);
-                    }
+                    createNewPidFile(autosaveDir, filename);
                     return result;
                 } else {
                     Main.warn(tr("Unable to create file {0}, other filename will be used", result.getAbsolutePath()));
-                    if (index > PROP_INDEX_LIMIT.get())
-                        throw new IOException("index limit exceeded");
                 }
             } catch (IOException e) {
@@ -179,4 +187,13 @@
             }
             index++;
+        }
+    }
+
+    private static void createNewPidFile(File autosaveDir, String filename) {
+        File pidFile = new File(autosaveDir, filename+".pid");
+        try (PrintStream ps = new PrintStream(pidFile, "UTF-8")) {
+            ps.println(ManagementFactory.getRuntimeMXBean().getName());
+        } catch (IOException | SecurityException t) {
+            Main.error(t);
         }
     }
@@ -188,5 +205,5 @@
         }
         if (changedDatasets.remove(info.layer.data)) {
-            File file = getNewLayerFile(info);
+            File file = getNewLayerFile(info, new Date(), 0);
             if (file != null) {
                 info.backupFiles.add(file);
@@ -240,7 +257,5 @@
         synchronized (layersLock) {
             layer.data.addDataSetListener(datasetAdapter);
-            AutosaveLayerInfo info = new AutosaveLayerInfo();
-            info.layer = layer;
-            layersInfo.add(info);
+            layersInfo.add(new AutosaveLayerInfo(layer));
         }
     }
@@ -287,5 +302,5 @@
     }
 
-    private File getPidFile(File osmFile) {
+    protected File getPidFile(File osmFile) {
         return new File(autosaveDir, osmFile.getName().replaceFirst("[.][^.]+$", ".pid"));
     }
Index: /trunk/test/unit/org/openstreetmap/josm/actions/ExtensionFileFilterTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/actions/ExtensionFileFilterTest.java	(revision 10298)
+++ /trunk/test/unit/org/openstreetmap/josm/actions/ExtensionFileFilterTest.java	(revision 10299)
@@ -2,7 +2,9 @@
 package org.openstreetmap.josm.actions;
 
-import nl.jqno.equalsverifier.EqualsVerifier;
+import static org.junit.Assert.assertEquals;
 
 import org.junit.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
 
 /**
@@ -10,4 +12,28 @@
  */
 public class ExtensionFileFilterTest {
+
+    private static void test(String extensions, String defaultExtension, String description, boolean addArchiveExtensionsToDescription,
+            String expectedExtensions, String expectedDescription) {
+        ExtensionFileFilter ext = ExtensionFileFilter.newFilterWithArchiveExtensions(
+                extensions, defaultExtension, description, addArchiveExtensionsToDescription);
+        assertEquals(expectedExtensions, ext.getExtensions());
+        assertEquals(defaultExtension, ext.getDefaultExtension());
+        assertEquals(expectedDescription, ext.getDescription());
+    }
+
+    /**
+     * Unit test of method {@link ExtensionFileFilter#newFilterWithArchiveExtensions}.
+     */
+    @Test
+    public void testNewFilterWithArchiveExtensions() {
+        test("ext1", "ext1", "description", true,
+                "ext1,ext1.gz,ext1.bz2", "description (*.ext1, *.ext1.gz, *.ext1.bz2)");
+        test("ext1", "ext1", "description", false,
+                "ext1,ext1.gz,ext1.bz2", "description (*.ext1)");
+        test("ext1,ext2", "ext1", "description", true,
+                "ext1,ext1.gz,ext1.bz2,ext2,ext2.gz,ext2.bz2", "description (*.ext1, *.ext1.gz, *.ext1.bz2, *.ext2, *.ext2.gz, *.ext2.bz2)");
+        test("ext1,ext2", "ext1", "description", false,
+                "ext1,ext1.gz,ext1.bz2,ext2,ext2.gz,ext2.bz2", "description (*.ext1, *.ext2)");
+    }
 
     /**
Index: /trunk/test/unit/org/openstreetmap/josm/actions/search/SearchCompilerTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/actions/search/SearchCompilerTest.java	(revision 10298)
+++ /trunk/test/unit/org/openstreetmap/josm/actions/search/SearchCompilerTest.java	(revision 10299)
@@ -427,4 +427,8 @@
         final SearchCompiler.Match c4 = SearchCompiler.compile("foo1 OR (bar1 bar2 baz1 XOR baz2) OR foo2");
         assertEquals("foo1 || (bar1 && bar2 && (baz1 ^ baz2)) || foo2", c4.toString());
+        final SearchCompiler.Match c5 = SearchCompiler.compile("foo1 XOR (baz1 XOR (bar baz))");
+        assertEquals("foo1 ^ baz1 ^ (bar && baz)", c5.toString());
+        final SearchCompiler.Match c6 = SearchCompiler.compile("foo1 XOR ((baz1 baz2) XOR (bar OR baz))");
+        assertEquals("foo1 ^ (baz1 && baz2) ^ (bar || baz)", c6.toString());
     }
 
Index: /trunk/test/unit/org/openstreetmap/josm/data/AutosaveTaskTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/data/AutosaveTaskTest.java	(revision 10299)
+++ /trunk/test/unit/org/openstreetmap/josm/data/AutosaveTaskTest.java	(revision 10299)
@@ -0,0 +1,119 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.openstreetmap.josm.JOSMFixture;
+import org.openstreetmap.josm.data.AutosaveTask.AutosaveLayerInfo;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+
+/**
+ * Unit tests for class {@link AutosaveTask}.
+ */
+public class AutosaveTaskTest {
+
+    private static AutosaveTask task;
+
+    /**
+     * Setup test.
+     * @throws IOException if autosave directory cannot be created
+     */
+    @BeforeClass
+    public static void setUpBeforeClass() throws IOException {
+        JOSMFixture.createUnitTestFixture().init(true);
+        task = new AutosaveTask();
+        Files.createDirectories(task.getAutosaveDir());
+    }
+
+    /**
+     * Unit test to {@link AutosaveTask#getUnsavedLayersFiles} - empty case
+     */
+    @Test
+    public void testGetUnsavedLayersFilesEmpty() {
+        assertTrue(task.getUnsavedLayersFiles().isEmpty());
+    }
+
+    /**
+     * Unit test to {@link AutosaveTask#getUnsavedLayersFiles} - non empty case
+     * @throws IOException in case of I/O error
+     */
+    @Test
+    public void testGetUnsavedLayersFilesNotEmpty() throws IOException {
+        String autodir = task.getAutosaveDir().toString();
+        File layer1 = Files.createFile(Paths.get(autodir, "layer1.osm")).toFile();
+        File layer2 = Files.createFile(Paths.get(autodir, "layer2.osm")).toFile();
+        File dir = Files.createDirectory(Paths.get(autodir, "dir.osm")).toFile();
+        try {
+            List<File> files = task.getUnsavedLayersFiles();
+            assertEquals(2, files.size());
+            assertTrue(files.contains(layer1));
+            assertTrue(files.contains(layer2));
+            assertFalse(files.contains(dir));
+        } finally {
+            Files.delete(dir.toPath());
+            Files.delete(layer2.toPath());
+            Files.delete(layer1.toPath());
+        }
+    }
+
+    /**
+     * Unit test to {@link AutosaveTask#getNewLayerFile}
+     * @throws IOException in case of I/O error
+     */
+    @Test
+    public void testGetNewLayerFile() throws IOException {
+        AutosaveLayerInfo info = new AutosaveLayerInfo(new OsmDataLayer(new DataSet(), "layer", null));
+        Calendar cal = Calendar.getInstance();
+        cal.set(2016, 0, 1, 1, 2, 3);
+        cal.set(Calendar.MILLISECOND, 456);
+        Date fixed = cal.getTime();
+
+        List<File> files = new ArrayList<>();
+
+        try {
+            for (int i = 0; i <= AutosaveTask.PROP_INDEX_LIMIT.get()+1; i++) {
+                // Only retry 2 indexes to avoid 1000*1000 disk operations
+                File f = task.getNewLayerFile(info, fixed, Math.max(0, i-2));
+                files.add(f);
+                if (i > AutosaveTask.PROP_INDEX_LIMIT.get()) {
+                    assertNull(f);
+                } else {
+                    assertNotNull(f);
+                    File pid = task.getPidFile(f);
+                    assertTrue(pid.exists());
+                    assertTrue(f.exists());
+                    if (i == 0) {
+                        assertEquals("null_20160101_010203456.osm", f.getName());
+                        assertEquals("null_20160101_010203456.pid", pid.getName());
+                    } else {
+                        assertEquals("null_20160101_010203456_"+i+".osm", f.getName());
+                        assertEquals("null_20160101_010203456_"+i+".pid", pid.getName());
+                    }
+                }
+            }
+        } finally {
+            for (File f : files) {
+                if (f != null) {
+                    Files.delete(task.getPidFile(f).toPath());
+                    Files.delete(f.toPath());
+                }
+            }
+        }
+    }
+}
