Index: /trunk/.classpath
===================================================================
--- /trunk/.classpath	(revision 14092)
+++ /trunk/.classpath	(revision 14093)
@@ -63,9 +63,4 @@
 		</attributes>
 	</classpathentry>
-	<classpathentry kind="lib" path="test/lib/unitils-core/unitils-core-3.4.6.jar">
-		<attributes>
-			<attribute name="test" value="true"/>
-		</attributes>
-	</classpathentry>
 	<classpathentry kind="lib" path="test/lib/commons-testing/commons-testing-2.1.0.jar">
 		<attributes>
@@ -89,20 +84,5 @@
 		</attributes>
 	</classpathentry>
-	<classpathentry kind="lib" path="test/lib/unitils-core/commons-collections-3.2.2.jar">
-		<attributes>
-			<attribute name="test" value="true"/>
-		</attributes>
-	</classpathentry>
-	<classpathentry kind="lib" path="test/lib/unitils-core/commons-lang-2.6.jar">
-		<attributes>
-			<attribute name="test" value="true"/>
-		</attributes>
-	</classpathentry>
-	<classpathentry kind="lib" path="test/lib/unitils-core/commons-logging-1.1.3.jar">
-		<attributes>
-			<attribute name="test" value="true"/>
-		</attributes>
-	</classpathentry>
-	<classpathentry kind="lib" path="test/lib/unitils-core/ognl-2.6.9.jar">
+	<classpathentry kind="lib" path="tools/pmd/commons-lang3-3.7.jar">
 		<attributes>
 			<attribute name="test" value="true"/>
Index: /trunk/build.xml
===================================================================
--- /trunk/build.xml	(revision 14092)
+++ /trunk/build.xml	(revision 14093)
@@ -501,5 +501,5 @@
                 <junit printsummary="yes" fork="true" forkmode="once">
                     <jvmarg value="-Dfile.encoding=UTF-8"/>
-                    <jvmarg value="-javaagent:${test.dir}/lib/jmockit-1.40.jar"/>
+                    <jvmarg value="-javaagent:${test.dir}/lib/jmockit-1.41.jar"/>
                     <jvmarg value="--add-modules" if:set="isJava9" unless:set="isJava11" />
                     <jvmarg value="java.activation,java.se.ee" if:set="isJava9" unless:set="isJava11" />
Index: /trunk/netbeans/nbproject/project.properties
===================================================================
--- /trunk/netbeans/nbproject/project.properties	(revision 14092)
+++ /trunk/netbeans/nbproject/project.properties	(revision 14093)
@@ -32,7 +32,5 @@
 endorsed.classpath=
 excludes=org/apache/commons/compress/compressors/CompressorException.java,org/apache/commons/compress/compressors/CompressorStreamFactory.java,org/apache/commons/compress/compressors/CompressorStreamProvider.java,org/apache/commons/compress/compressors/FileNameUtil.java,org/apache/commons/compress/compressors/brotli/**,org/apache/commons/compress/compressors/bzip2/BZip2Utils.java,org/apache/commons/compress/compressors/deflate/**,org/apache/commons/compress/compressors/gzip/**,org/apache/commons/compress/compressors/lz4/**,org/apache/commons/compress/compressors/lz77support/**,org/apache/commons/compress/compressors/lzma/**,org/apache/commons/compress/compressors/pack200/**,org/apache/commons/compress/compressors/snappy/**,org/apache/commons/compress/compressors/xz/XZUtils.java,org/apache/commons/compress/compressors/z/**,org/apache/commons/compress/compressors/zstandard/**,org/apache/commons/compress/utils/ArchiveUtils.java,org/apache/commons/jcs/JCS.java,org/apache/commons/jcs/access/GroupCacheAccess.java,org/apache/commons/jcs/access/PartitionedCacheAccess.java,org/apache/commons/jcs/access/behavior/IGroupCacheAccess.java,org/apache/commons/jcs/access/exception/InvalidGroupException.java,org/apache/commons/jcs/admin/servlet/**,org/apache/commons/jcs/auxiliary/AbstractAuxiliaryCacheMonitor.java,org/apache/commons/jcs/auxiliary/disk/jdbc/**,org/apache/commons/jcs/auxiliary/lateral/**,org/apache/commons/jcs/auxiliary/remote/AbstractRemoteAuxiliaryCache.java,org/apache/commons/jcs/auxiliary/remote/AbstractRemoteCacheListener.java,org/apache/commons/jcs/auxiliary/remote/AbstractRemoteCacheNoWaitFacade.java,org/apache/commons/jcs/auxiliary/remote/RemoteCache.java,org/apache/commons/jcs/auxiliary/remote/RemoteCacheFactory.java,org/apache/commons/jcs/auxiliary/remote/RemoteCacheFailoverRunner.java,org/apache/commons/jcs/auxiliary/remote/RemoteCacheListener.java,org/apache/commons/jcs/auxiliary/remote/RemoteCacheManager.java,org/apache/commons/jcs/auxiliary/remote/RemoteCacheMonitor.java,org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWait.java,org/apache/commons/jcs/auxiliary/remote/RemoteCacheNoWaitFacade.java,org/apache/commons/jcs/auxiliary/remote/RemoteCacheRestore.java,org/apache/commons/jcs/auxiliary/remote/http/**,org/apache/commons/jcs/auxiliary/remote/server/RemoteCacheStartupServlet.java,org/apache/commons/jcs/auxiliary/remote/server/TimeoutConfigurableRMISocketFactory.java,org/apache/commons/jcs/engine/CacheAdaptor.java,org/apache/commons/jcs/engine/CacheGroup.java,org/apache/commons/jcs/engine/CacheWatchRepairable.java,org/apache/commons/jcs/engine/ZombieCacheService.java,org/apache/commons/jcs/engine/ZombieCacheServiceNonLocal.java,org/apache/commons/jcs/engine/ZombieCacheWatch.java,org/apache/commons/jcs/engine/logging/CacheEventLoggerDebugLogger.java,org/apache/commons/jcs/utils/access/**,org/apache/commons/jcs/utils/discovery/**,org/apache/commons/jcs/utils/net/**,org/apache/commons/jcs/utils/props/**,org/apache/commons/jcs/utils/servlet/**,org/apache/commons/logging/impl/AvalonLogger.java,org/apache/commons/logging/impl/Jdk13LumberjackLogger.java,org/apache/commons/logging/impl/Log4JLogger.java,org/apache/commons/logging/impl/LogKitLogger.java,org/apache/commons/logging/impl/ServletContextCleaner.java,org/openstreetmap/gui/jmapviewer/Demo.java,org/openstreetmap/gui/jmapviewer/JMapViewerTree.java,org/openstreetmap/gui/jmapviewer/checkBoxTree/**,org/apache/commons/compress/archivers/**,org/apache/commons/compress/changes/**,org/apache/commons/compress/parallel/**,org/apache/commons/compress/PasswordRequiredException.java
-file.reference.commons-collections-3.2.2.jar=../test/lib/unitils-core/commons-collections-3.2.2.jar
-file.reference.commons-lang-2.6.jar=../test/lib/unitils-core/commons-lang-2.6.jar
-file.reference.commons-logging-1.1.3.jar=../test/lib/unitils-core/commons-logging-1.1.3.jar
+file.reference.commons-lang-3.7.jar=../tools/pmd/commons-lang-3.7.jar
 file.reference.commons-testing-2.1.0.jar=../test/lib/commons-testing/commons-testing-2.1.0.jar
 file.reference.core-src=../src
@@ -45,10 +43,8 @@
 file.reference.jmockit-1.41.jar=../test/lib/jmockit-1.41.jar
 file.reference.junit-4.12.jar=../test/lib/junit/junit-4.12.jar
-file.reference.ognl-2.6.9.jar=../test/lib/unitils-core/ognl-2.6.9.jar
 file.reference.reflections-0.9.10.jar=../test/lib/reflections/reflections-0.9.10.jar
 file.reference.test-functional=../test/functional
 file.reference.test-performance=../test/performance
 file.reference.test-unit=../test/unit
-file.reference.unitils-core-3.4.6.jar=../test/lib/unitils-core/unitils-core-3.4.6.jar
 file.reference.system-rules-1.16.1.jar=../test/lib/system-rules-1.16.1.jar
 file.reference.wiremock-standalone-2.18.0.jar=../test/lib/wiremock-standalone-2.18.0.jar
@@ -76,9 +72,5 @@
     ${file.reference.javassist-3.21.0-GA.jar}:\
     ${file.reference.reflections-0.9.11.jar}:\
-    ${file.reference.commons-collections-3.2.2.jar}:\
-    ${file.reference.commons-lang-2.6.jar}:\
-    ${file.reference.commons-logging-1.1.3.jar}:\
-    ${file.reference.ognl-2.6.9.jar}:\
-    ${file.reference.unitils-core-3.4.6.jar}:\
+    ${file.reference.commons-lang-3.7.jar}:\
     ${file.reference.system-rules-1.16.1.jar}:\
     ${file.reference.wiremock-standalone-2.18.0.jar}:\
Index: /trunk/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java	(revision 14092)
+++ /trunk/src/org/openstreetmap/josm/data/osm/search/SearchCompiler.java	(revision 14093)
@@ -322,4 +322,24 @@
             return match;
         }
+
+        @Override
+        public int hashCode() {
+            return 31 + ((match == null) ? 0 : match.hashCode());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            UnaryMatch other = (UnaryMatch) obj;
+            if (match == null) {
+                if (other.match != null)
+                    return false;
+            } else if (!match.equals(other.match))
+                return false;
+            return true;
+        }
     }
 
@@ -361,4 +381,33 @@
             return '(' + m.toString() + ')';
         }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((lhs == null) ? 0 : lhs.hashCode());
+            result = prime * result + ((rhs == null) ? 0 : rhs.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            AbstractBinaryMatch other = (AbstractBinaryMatch) obj;
+            if (lhs == null) {
+                if (other.lhs != null)
+                    return false;
+            } else if (!lhs.equals(other.lhs))
+                return false;
+            if (rhs == null) {
+                if (other.rhs != null)
+                    return false;
+            } else if (!rhs.equals(other.rhs))
+                return false;
+            return true;
+        }
     }
 
@@ -435,4 +484,30 @@
         public String toString() {
             return key + '?';
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (defaultValue ? 1231 : 1237);
+            result = prime * result + ((key == null) ? 0 : key.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            BooleanMatch other = (BooleanMatch) obj;
+            if (defaultValue != other.defaultValue)
+                return false;
+            if (key == null) {
+                if (other.key != null)
+                    return false;
+            } else if (!key.equals(other.key))
+                return false;
+            return true;
         }
     }
@@ -700,4 +775,48 @@
             return key + '=' + value;
         }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (caseSensitive ? 1231 : 1237);
+            result = prime * result + ((key == null) ? 0 : key.hashCode());
+            result = prime * result + ((keyPattern == null) ? 0 : keyPattern.hashCode());
+            result = prime * result + ((value == null) ? 0 : value.hashCode());
+            result = prime * result + ((valuePattern == null) ? 0 : valuePattern.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            KeyValue other = (KeyValue) obj;
+            if (caseSensitive != other.caseSensitive)
+                return false;
+            if (key == null) {
+                if (other.key != null)
+                    return false;
+            } else if (!key.equals(other.key))
+                return false;
+            if (keyPattern == null) {
+                if (other.keyPattern != null)
+                    return false;
+            } else if (!keyPattern.equals(other.keyPattern))
+                return false;
+            if (value == null) {
+                if (other.value != null)
+                    return false;
+            } else if (!value.equals(other.value))
+                return false;
+            if (valuePattern == null) {
+                if (other.valuePattern != null)
+                    return false;
+            } else if (!valuePattern.equals(other.valuePattern))
+                return false;
+            return true;
+        }
     }
 
@@ -747,4 +866,42 @@
         public String toString() {
             return key + (compareMode == -1 ? "<" : compareMode == +1 ? ">" : "") + referenceValue;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + compareMode;
+            result = prime * result + ((key == null) ? 0 : key.hashCode());
+            result = prime * result + ((referenceNumber == null) ? 0 : referenceNumber.hashCode());
+            result = prime * result + ((referenceValue == null) ? 0 : referenceValue.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            ValueComparison other = (ValueComparison) obj;
+            if (compareMode != other.compareMode)
+                return false;
+            if (key == null) {
+                if (other.key != null)
+                    return false;
+            } else if (!key.equals(other.key))
+                return false;
+            if (referenceNumber == null) {
+                if (other.referenceNumber != null)
+                    return false;
+            } else if (!referenceNumber.equals(other.referenceNumber))
+                return false;
+            if (referenceValue == null) {
+                if (other.referenceValue != null)
+                    return false;
+            } else if (!referenceValue.equals(other.referenceValue))
+                return false;
+            return true;
         }
     }
@@ -883,4 +1040,48 @@
             return key + '=' + value;
         }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + ((key == null) ? 0 : key.hashCode());
+            result = prime * result + ((keyPattern == null) ? 0 : keyPattern.hashCode());
+            result = prime * result + ((mode == null) ? 0 : mode.hashCode());
+            result = prime * result + ((value == null) ? 0 : value.hashCode());
+            result = prime * result + ((valuePattern == null) ? 0 : valuePattern.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            ExactKeyValue other = (ExactKeyValue) obj;
+            if (key == null) {
+                if (other.key != null)
+                    return false;
+            } else if (!key.equals(other.key))
+                return false;
+            if (keyPattern == null) {
+                if (other.keyPattern != null)
+                    return false;
+            } else if (!keyPattern.equals(other.keyPattern))
+                return false;
+            if (mode != other.mode)
+                return false;
+            if (value == null) {
+                if (other.value != null)
+                    return false;
+            } else if (!value.equals(other.value))
+                return false;
+            if (valuePattern == null) {
+                if (other.valuePattern != null)
+                    return false;
+            } else if (!valuePattern.equals(other.valuePattern))
+                return false;
+            return true;
+        }
     }
 
@@ -954,4 +1155,36 @@
             return search;
         }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (caseSensitive ? 1231 : 1237);
+            result = prime * result + ((search == null) ? 0 : search.hashCode());
+            result = prime * result + ((searchRegex == null) ? 0 : searchRegex.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            Any other = (Any) obj;
+            if (caseSensitive != other.caseSensitive)
+                return false;
+            if (search == null) {
+                if (other.search != null)
+                    return false;
+            } else if (!search.equals(other.search))
+                return false;
+            if (searchRegex == null) {
+                if (other.searchRegex != null)
+                    return false;
+            } else if (!searchRegex.equals(other.searchRegex))
+                return false;
+            return true;
+        }
     }
 
@@ -973,4 +1206,21 @@
         public String toString() {
             return "type=" + type;
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 + ((type == null) ? 0 : type.hashCode());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            ExactType other = (ExactType) obj;
+            if (type != other.type)
+                return false;
+            return true;
         }
     }
@@ -1001,4 +1251,24 @@
         public String toString() {
             return "user=" + (user == null ? "" : user);
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 + ((user == null) ? 0 : user.hashCode());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            UserMatch other = (UserMatch) obj;
+            if (user == null) {
+                if (other.user != null)
+                    return false;
+            } else if (!user.equals(other.user))
+                return false;
+            return true;
         }
     }
@@ -1037,4 +1307,24 @@
         public String toString() {
             return "role=" + role;
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 + ((role == null) ? 0 : role.hashCode());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            RoleMatch other = (RoleMatch) obj;
+            if (role == null) {
+                if (other.role != null)
+                    return false;
+            } else if (!role.equals(other.role))
+                return false;
+            return true;
         }
     }
@@ -1085,4 +1375,27 @@
             return "Nth{nth=" + nth + ", modulo=" + modulo + '}';
         }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (modulo ? 1231 : 1237);
+            result = prime * result + nth;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            Nth other = (Nth) obj;
+            if (modulo != other.modulo)
+                return false;
+            if (nth != other.nth)
+                return false;
+            return true;
+        }
     }
 
@@ -1120,4 +1433,27 @@
         public String toString() {
             return getString() + '=' + min + '-' + max;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (int) (max ^ (max >>> 32));
+            result = prime * result + (int) (min ^ (min >>> 32));
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            RangeMatch other = (RangeMatch) obj;
+            if (max != other.max)
+                return false;
+            if (min != other.min)
+                return false;
+            return true;
         }
     }
@@ -1237,4 +1573,24 @@
         public boolean match(OsmPrimitive osm) {
             return osm instanceof Relation && ((Relation) osm).getMemberRoles().contains(role);
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 + ((role == null) ? 0 : role.hashCode());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            HasRole other = (HasRole) obj;
+            if (role == null) {
+                if (other.role != null)
+                    return false;
+            } else if (!role.equals(other.role))
+                return false;
+            return true;
         }
     }
@@ -1490,4 +1846,21 @@
                 return false;
         }
+
+        @Override
+        public int hashCode() {
+            return 31 + (all ? 1231 : 1237);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            InArea other = (InArea) obj;
+            if (all != other.all)
+                return false;
+            return true;
+        }
     }
 
@@ -1599,4 +1972,24 @@
                 return false;
             }
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 + ((presets == null) ? 0 : presets.hashCode());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            Preset other = (Preset) obj;
+            if (presets == null) {
+                if (other.presets != null)
+                    return false;
+            } else if (!presets.equals(other.presets))
+                return false;
+            return true;
         }
     }
Index: /trunk/src/org/openstreetmap/josm/tools/template_engine/CompoundTemplateEntry.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/template_engine/CompoundTemplateEntry.java	(revision 14092)
+++ /trunk/src/org/openstreetmap/josm/tools/template_engine/CompoundTemplateEntry.java	(revision 14093)
@@ -1,4 +1,6 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.tools.template_engine;
+
+import java.util.Arrays;
 
 /**
@@ -54,3 +56,20 @@
         return result.toString();
     }
+
+    @Override
+    public int hashCode() {
+        return 31 + Arrays.hashCode(entries);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null || getClass() != obj.getClass())
+            return false;
+        CompoundTemplateEntry other = (CompoundTemplateEntry) obj;
+        if (!Arrays.equals(entries, other.entries))
+            return false;
+        return true;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/tools/template_engine/Condition.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/template_engine/Condition.java	(revision 14092)
+++ /trunk/src/org/openstreetmap/josm/tools/template_engine/Condition.java	(revision 14093)
@@ -71,3 +71,23 @@
         return sb.toString();
     }
+
+    @Override
+    public int hashCode() {
+        return 31 + ((entries == null) ? 0 : entries.hashCode());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null || getClass() != obj.getClass())
+            return false;
+        Condition other = (Condition) obj;
+        if (entries == null) {
+            if (other.entries != null)
+                return false;
+        } else if (!entries.equals(other.entries))
+            return false;
+        return true;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/tools/template_engine/ContextSwitchTemplate.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/template_engine/ContextSwitchTemplate.java	(revision 14092)
+++ /trunk/src/org/openstreetmap/josm/tools/template_engine/ContextSwitchTemplate.java	(revision 14093)
@@ -49,4 +49,24 @@
 
         abstract List<OsmPrimitive> getPrimitives(OsmPrimitive root);
+
+        @Override
+        public int hashCode() {
+            return 31 + ((condition == null) ? 0 : condition.hashCode());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null || getClass() != obj.getClass())
+                return false;
+            ContextProvider other = (ContextProvider) obj;
+            if (condition == null) {
+                if (other.condition != null)
+                    return false;
+            } else if (!condition.equals(other.condition))
+                return false;
+            return true;
+        }
     }
 
@@ -83,4 +103,24 @@
             }
             return result;
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * super.hashCode() + ((childCondition == null) ? 0 : childCondition.hashCode());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!super.equals(obj) || getClass() != obj.getClass())
+                return false;
+            ParentSet other = (ParentSet) obj;
+            if (childCondition == null) {
+                if (other.childCondition != null)
+                    return false;
+            } else if (!childCondition.equals(other.childCondition))
+                return false;
+            return true;
         }
     }
@@ -127,4 +167,24 @@
             return result;
         }
+
+        @Override
+        public int hashCode() {
+            return 31 * super.hashCode() + ((parentCondition == null) ? 0 : parentCondition.hashCode());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!super.equals(obj) || getClass() != obj.getClass())
+                return false;
+            ChildSet other = (ChildSet) obj;
+            if (parentCondition == null) {
+                if (other.parentCondition != null)
+                    return false;
+            } else if (!parentCondition.equals(other.parentCondition))
+                return false;
+            return true;
+        }
     }
 
@@ -157,4 +217,33 @@
             }
             return result;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = super.hashCode();
+            result = prime * result + ((lhs == null) ? 0 : lhs.hashCode());
+            result = prime * result + ((rhs == null) ? 0 : rhs.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!super.equals(obj) || getClass() != obj.getClass())
+                return false;
+            OrSet other = (OrSet) obj;
+            if (lhs == null) {
+                if (other.lhs != null)
+                    return false;
+            } else if (!lhs.equals(other.lhs))
+                return false;
+            if (rhs == null) {
+                if (other.rhs != null)
+                    return false;
+            } else if (!rhs.equals(other.rhs))
+                return false;
+            return true;
         }
     }
@@ -184,4 +273,33 @@
             }
             return result;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = super.hashCode();
+            result = prime * result + ((lhs == null) ? 0 : lhs.hashCode());
+            result = prime * result + ((rhs == null) ? 0 : rhs.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (!super.equals(obj) || getClass() != obj.getClass())
+                return false;
+            AndSet other = (AndSet) obj;
+            if (lhs == null) {
+                if (other.lhs != null)
+                    return false;
+            } else if (!lhs.equals(other.lhs))
+                return false;
+            if (rhs == null) {
+                if (other.rhs != null)
+                    return false;
+            } else if (!rhs.equals(other.rhs))
+                return false;
+            return true;
         }
     }
@@ -289,3 +407,32 @@
         return false;
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((context == null) ? 0 : context.hashCode());
+        result = prime * result + ((template == null) ? 0 : template.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null || getClass() != obj.getClass())
+            return false;
+        ContextSwitchTemplate other = (ContextSwitchTemplate) obj;
+        if (context == null) {
+            if (other.context != null)
+                return false;
+        } else if (!context.equals(other.context))
+            return false;
+        if (template == null) {
+            if (other.template != null)
+                return false;
+        } else if (!template.equals(other.template))
+            return false;
+        return true;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/tools/template_engine/ParseError.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/template_engine/ParseError.java	(revision 14092)
+++ /trunk/src/org/openstreetmap/josm/tools/template_engine/ParseError.java	(revision 14093)
@@ -17,4 +17,8 @@
     private final transient Token unexpectedToken;
 
+    /**
+     * Constructs a new {@code ParseError} for an unexpected token.
+     * @param unexpectedToken the unexpected token
+     */
     public ParseError(Token unexpectedToken) {
         super(tr("Unexpected token ({0}) on position {1}", unexpectedToken.getType(), unexpectedToken.getPosition()));
@@ -22,4 +26,9 @@
     }
 
+    /**
+     * Constructs a new {@code ParseError} for an unexpected token and an expected token.
+     * @param unexpectedToken the unexpected token
+     * @param expected the expected token
+     */
     public ParseError(Token unexpectedToken, TokenType expected) {
         super(tr("Unexpected token on position {0}. Expected {1}, found {2}",
@@ -28,4 +37,9 @@
     }
 
+    /**
+     * Constructs a new {@code ParseError} from a {@link SearchParseError}.
+     * @param position the position
+     * @param e the cause
+     */
     public ParseError(int position, SearchParseError e) {
         super(tr("Error while parsing search expression on position {0}", position), e);
@@ -33,4 +47,8 @@
     }
 
+    /**
+     * Constructs a new {@code ParseError} with a generic message.
+     * @param message the detail message. The detail message is saved for later retrieval by the {@link #getMessage()} method.
+     */
     public ParseError(String message) {
         super(message);
@@ -38,8 +56,19 @@
     }
 
+    /**
+     * Returns the unexpected token, if any.
+     * @return the unexpected token, or null
+     */
     public Token getUnexpectedToken() {
         return unexpectedToken;
     }
 
+    /**
+     * Constructs a new {@code ParseError} for an unexpected character.
+     * @param expected the expected character
+     * @param found the found character
+     * @param position the position
+     * @return a new {@code ParseError}
+     */
     public static ParseError unexpectedChar(char expected, char found, int position) {
         return new ParseError(tr("Unexpected char on {0}. Expected {1} found {2}", position, expected, found));
Index: /trunk/src/org/openstreetmap/josm/tools/template_engine/SearchExpressionCondition.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/template_engine/SearchExpressionCondition.java	(revision 14092)
+++ /trunk/src/org/openstreetmap/josm/tools/template_engine/SearchExpressionCondition.java	(revision 14093)
@@ -37,3 +37,32 @@
         return condition + " '" + text + '\'';
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((condition == null) ? 0 : condition.hashCode());
+        result = prime * result + ((text == null) ? 0 : text.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null || getClass() != obj.getClass())
+            return false;
+        SearchExpressionCondition other = (SearchExpressionCondition) obj;
+        if (condition == null) {
+            if (other.condition != null)
+                return false;
+        } else if (!condition.equals(other.condition))
+            return false;
+        if (text == null) {
+            if (other.text != null)
+                return false;
+        } else if (!text.equals(other.text))
+            return false;
+        return true;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/tools/template_engine/StaticText.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/template_engine/StaticText.java	(revision 14092)
+++ /trunk/src/org/openstreetmap/josm/tools/template_engine/StaticText.java	(revision 14093)
@@ -33,3 +33,23 @@
         return staticText;
     }
+
+    @Override
+    public int hashCode() {
+        return 31 + ((staticText == null) ? 0 : staticText.hashCode());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null || getClass() != obj.getClass())
+            return false;
+        StaticText other = (StaticText) obj;
+        if (staticText == null) {
+            if (other.staticText != null)
+                return false;
+        } else if (!staticText.equals(other.staticText))
+            return false;
+        return true;
+    }
 }
Index: /trunk/src/org/openstreetmap/josm/tools/template_engine/Variable.java
===================================================================
--- /trunk/src/org/openstreetmap/josm/tools/template_engine/Variable.java	(revision 14092)
+++ /trunk/src/org/openstreetmap/josm/tools/template_engine/Variable.java	(revision 14093)
@@ -80,3 +80,29 @@
         return special;
     }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (special ? 1231 : 1237);
+        result = prime * result + ((variableName == null) ? 0 : variableName.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null || getClass() != obj.getClass())
+            return false;
+        Variable other = (Variable) obj;
+        if (special != other.special)
+            return false;
+        if (variableName == null) {
+            if (other.variableName != null)
+                return false;
+        } else if (!variableName.equals(other.variableName))
+            return false;
+        return true;
+    }
 }
Index: /trunk/test/performance/org/openstreetmap/josm/data/osm/KeyValuePerformanceTest.java
===================================================================
--- /trunk/test/performance/org/openstreetmap/josm/data/osm/KeyValuePerformanceTest.java	(revision 14092)
+++ /trunk/test/performance/org/openstreetmap/josm/data/osm/KeyValuePerformanceTest.java	(revision 14093)
@@ -12,5 +12,5 @@
 import java.util.Random;
 
-import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang3.RandomStringUtils;
 import org.junit.Before;
 import org.junit.Rule;
Index: /trunk/test/performance/org/openstreetmap/josm/data/osm/OsmDataGenerator.java
===================================================================
--- /trunk/test/performance/org/openstreetmap/josm/data/osm/OsmDataGenerator.java	(revision 14092)
+++ /trunk/test/performance/org/openstreetmap/josm/data/osm/OsmDataGenerator.java	(revision 14093)
@@ -7,5 +7,5 @@
 import java.util.Random;
 
-import org.apache.commons.lang.RandomStringUtils;
+import org.apache.commons.lang3.RandomStringUtils;
 import org.openstreetmap.josm.data.coor.EastNorth;
 import org.openstreetmap.josm.gui.layer.OsmDataLayer;
Index: /trunk/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java	(revision 14092)
+++ /trunk/test/unit/org/openstreetmap/josm/plugins/PluginHandlerTestIT.java	(revision 14093)
@@ -16,6 +16,5 @@
 import java.util.stream.Collectors;
 
-import org.apache.commons.collections.MapUtils;
-import org.apache.commons.lang.exception.ExceptionUtils;
+import org.apache.commons.lang3.exception.ExceptionUtils;
 import org.junit.Rule;
 import org.junit.Test;
@@ -72,11 +71,18 @@
         }
 
-        MapUtils.debugPrint(System.out, null, invalidManifestEntries);
-        MapUtils.debugPrint(System.out, null, loadingExceptions);
-        MapUtils.debugPrint(System.out, null, layerExceptions);
+        debugPrint(invalidManifestEntries);
+        debugPrint(loadingExceptions);
+        debugPrint(layerExceptions);
         String msg = Arrays.toString(invalidManifestEntries.entrySet().toArray()) + '\n' +
                      Arrays.toString(loadingExceptions.entrySet().toArray()) + '\n' +
                      Arrays.toString(layerExceptions.entrySet().toArray());
         assertTrue(msg, invalidManifestEntries.isEmpty() && loadingExceptions.isEmpty() && layerExceptions.isEmpty());
+    }
+
+    private static void debugPrint(Map<String, ?> invalidManifestEntries) {
+        System.out.println(invalidManifestEntries.entrySet()
+                .stream()
+                .map(e -> e.getKey() + "=\"" + e.getValue() + "\"")
+                .collect(Collectors.joining(", ")));
     }
 
Index: /trunk/test/unit/org/openstreetmap/josm/tools/template_engine/TemplateParserTest.java
===================================================================
--- /trunk/test/unit/org/openstreetmap/josm/tools/template_engine/TemplateParserTest.java	(revision 14092)
+++ /trunk/test/unit/org/openstreetmap/josm/tools/template_engine/TemplateParserTest.java	(revision 14093)
@@ -1,4 +1,6 @@
 // License: GPL. For details, see LICENSE file.
 package org.openstreetmap.josm.tools.template_engine;
+
+import static org.junit.Assert.assertEquals;
 
 import java.util.Arrays;
@@ -16,5 +18,4 @@
 import org.openstreetmap.josm.data.osm.search.SearchParseError;
 import org.openstreetmap.josm.testutils.DatasetFactory;
-import org.unitils.reflectionassert.ReflectionAssert;
 
 /**
@@ -38,5 +39,5 @@
     public void testEmpty() throws ParseError {
         TemplateParser parser = new TemplateParser("");
-        ReflectionAssert.assertReflectionEquals(new StaticText(""), parser.parse());
+        assertEquals(new StaticText(""), parser.parse());
     }
 
@@ -48,5 +49,5 @@
     public void testVariable() throws ParseError {
         TemplateParser parser = new TemplateParser("abc{var}\\{ef\\$\\{g");
-        ReflectionAssert.assertReflectionEquals(CompoundTemplateEntry.fromArray(new StaticText("abc"),
+        assertEquals(CompoundTemplateEntry.fromArray(new StaticText("abc"),
                 new Variable("var"), new StaticText("{ef${g")), parser.parse());
     }
@@ -63,5 +64,5 @@
             new Variable("name"),
             new Variable("desc")));
-        ReflectionAssert.assertReflectionEquals(condition, parser.parse());
+        assertEquals(condition, parser.parse());
     }
 
@@ -77,5 +78,5 @@
                 new Variable("name"),
                 new Variable("desc")));
-        ReflectionAssert.assertReflectionEquals(condition, parser.parse());
+        assertEquals(condition, parser.parse());
     }
 
@@ -96,5 +97,6 @@
                 new SearchExpressionCondition(compile("admin_level = 4"), new StaticText("NUTS 2")),
                 new Variable("admin_level")));
-        ReflectionAssert.assertReflectionEquals(condition, parser.parse());
+        TemplateEntry parse = parser.parse();
+        assertEquals(condition, parse);
     }
 
