Index: /applications/editors/josm/plugins/eventbus/.checkstyle
===================================================================
--- /applications/editors/josm/plugins/eventbus/.checkstyle	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/.checkstyle	(revision 34000)
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
+  <local-check-config name="JOSM" location="/JOSM/tools/checkstyle/josm_checks.xml" type="project" description="">
+    <additional-data name="protect-config-file" value="false"/>
+  </local-check-config>
+  <fileset name="all" enabled="true" check-config-name="JOSM Checkstyle checks" local="false">
+    <file-match-pattern match-pattern="." include-pattern="true"/>
+  </fileset>
+  <filter name="DerivedFiles" enabled="true"/>
+  <filter name="FilesFromPackage" enabled="true">
+    <filter-data value="src/org/openstreetmap/josm/eventbus"/>
+    <filter-data value="test/unit"/>
+    <filter-data value="data"/>
+    <filter-data value="images"/>
+    <filter-data value="includes"/>
+    <filter-data value="styles"/>
+    <filter-data value="resources"/>
+    <filter-data value="scripts"/>
+  </filter>
+</fileset-config>
Index: /applications/editors/josm/plugins/eventbus/.classpath
===================================================================
--- /applications/editors/josm/plugins/eventbus/.classpath	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/.classpath	(revision 34000)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="test/unit"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/JOSM"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
Index: /applications/editors/josm/plugins/eventbus/.project
===================================================================
--- /applications/editors/josm/plugins/eventbus/.project	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/.project	(revision 34000)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>JOSM-eventbus</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>net.sf.eclipsecs.core.CheckstyleBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>net.sf.eclipsecs.core.CheckstyleNature</nature>
+	</natures>
+</projectDescription>
Index: /applications/editors/josm/plugins/eventbus/.settings/org.eclipse.jdt.core.prefs
===================================================================
--- /applications/editors/josm/plugins/eventbus/.settings/org.eclipse.jdt.core.prefs	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/.settings/org.eclipse.jdt.core.prefs	(revision 34000)
@@ -0,0 +1,120 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.APILeak=warning
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=warning
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=warning
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsMethodTypeParameters=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=warning
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
+org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.source=1.8
Index: /applications/editors/josm/plugins/eventbus/.settings/org.eclipse.jdt.ui.prefs
===================================================================
--- /applications/editors/josm/plugins/eventbus/.settings/org.eclipse.jdt.ui.prefs	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/.settings/org.eclipse.jdt.ui.prefs	(revision 34000)
@@ -0,0 +1,59 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=false
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=true
+sp_cleanup.convert_to_enhanced_for_loop=true
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=false
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=true
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=true
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=true
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
Index: /applications/editors/josm/plugins/eventbus/GPL-v2.0.txt
===================================================================
--- /applications/editors/josm/plugins/eventbus/GPL-v2.0.txt	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/GPL-v2.0.txt	(revision 34000)
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
Index: /applications/editors/josm/plugins/eventbus/GPL-v3.0.txt
===================================================================
--- /applications/editors/josm/plugins/eventbus/GPL-v3.0.txt	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/GPL-v3.0.txt	(revision 34000)
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
Index: /applications/editors/josm/plugins/eventbus/README
===================================================================
--- /applications/editors/josm/plugins/eventbus/README	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/README	(revision 34000)
@@ -0,0 +1,12 @@
+README 
+======
+
+Readme for the Event Bus plugin.
+Provides an event bus more powerful than the traditional listeners registration.
+
+Inspired by the Google Guava EventBus https://github.com/google/guava/wiki/EventBusExplained
+
+    * Author: Vincent Privat <vincent.privat@gmail.com>
+    
+    * License: JOSM license ("GPL v2 or later").
+ 
Index: /applications/editors/josm/plugins/eventbus/build.xml
===================================================================
--- /applications/editors/josm/plugins/eventbus/build.xml	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/build.xml	(revision 34000)
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<project name="eventbus" default="dist" basedir=".">
+
+    <!-- enter the SVN commit message -->
+    <property name="commit.message" value="Commit message"/>
+    <!-- enter the *lowest* JOSM version this plugin is currently compatible with -->
+    <property name="plugin.main.version" value="13265"/>
+
+    <!-- Configure these properties (replace "..." accordingly).
+         See https://josm.openstreetmap.de/wiki/DevelopersGuide/DevelopingPlugins
+    -->
+    <property name="plugin.author" value="Don-vip"/>
+    <property name="plugin.class" value="org.openstreetmap.josm.plugins.eventbus.EventBusPlugin"/>
+    <property name="plugin.description" value="Provides an event bus more powerful than the traditional listeners registration"/>
+	<!--<property name="plugin.icon" value="..."/>-->
+    <!--<property name="plugin.link" value="..."/>-->
+    <!--<property name="plugin.early" value="..."/>-->
+    <!--<property name="plugin.requires" value="..."/>-->
+    <property name="plugin.stage" value="1"/>
+
+    <!-- ** include targets that all plugins have in common ** -->
+    <import file="../build-common.xml"/>
+  
+</project>
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/actions/ExpertModeChangedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/actions/ExpertModeChangedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/actions/ExpertModeChangedEvent.java	(revision 34000)
@@ -0,0 +1,32 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.actions;
+
+import java.util.EventObject;
+
+/**
+ * Event fired whenever the expert mode setting changed.
+ */
+public class ExpertModeChangedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final boolean isExpert;
+
+    /**
+     * Constructs a new {@code ExpertModeChangedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param isExpert {@code true} if expert mode has been enabled, false otherwise
+     */
+    public ExpertModeChangedEvent(Object source, boolean isExpert) {
+        super(source);
+        this.isExpert = isExpert;
+    }
+
+    /**
+     * Determines if expert mode has been enabled.
+     * @return {@code true} if expert mode has been enabled, false otherwise
+     */
+    public boolean isExpert() {
+        return isExpert;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/CommandQueueEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/CommandQueueEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/CommandQueueEvent.java	(revision 34000)
@@ -0,0 +1,43 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when the command queue (undo/redo) size changes.
+ */
+public class CommandQueueEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final int queueSize;
+    private final int redoSize;
+
+    /**
+     * Constructs a new {@code CommandQueueEvent}.
+     * @param source object on which the Event initially occurred
+     * @param queueSize Undo stack size
+     * @param redoSize Redo stack size
+     */
+    public CommandQueueEvent(Object source, int queueSize, int redoSize) {
+        super(source);
+        this.queueSize = queueSize;
+        this.redoSize = redoSize;
+    }
+
+    /**
+     * Returns Undo stack size.
+     * @return Undo stack size
+     */
+    public final int getQueueSize() {
+        return queueSize;
+    }
+
+    /**
+     * Returns Redo stack size.
+     * @return Redo stack size
+     */
+    public final int getRedoSize() {
+        return redoSize;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/SoMChangedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/SoMChangedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/SoMChangedEvent.java	(revision 34000)
@@ -0,0 +1,44 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data;
+
+import java.util.EventObject;
+import java.util.Objects;
+
+/**
+ * Event fired when the syste of measurement changes.
+ */
+public class SoMChangedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final String oldSoM;
+    private final String newSoM;
+
+    /**
+     * Constructs a new {@code SoMChangedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param oldSoM old system of measurement
+     * @param newSoM new system of measurement
+     */
+    public SoMChangedEvent(Object source, String oldSoM, String newSoM) {
+        super(source);
+        this.oldSoM = Objects.requireNonNull(oldSoM);
+        this.newSoM = Objects.requireNonNull(newSoM);
+    }
+
+    /**
+     * Returns the old system of measurement.
+     * @return the old system of measurement
+     */
+    public final String getOldSoM() {
+        return oldSoM;
+    }
+
+    /**
+     * Returns the new system of measurement.
+     * @return the new system of measurement
+     */
+    public final String getNewSoM() {
+        return newSoM;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/conflict/AbstractConflictsEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/conflict/AbstractConflictsEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/conflict/AbstractConflictsEvent.java	(revision 34000)
@@ -0,0 +1,33 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.conflict;
+
+import java.util.EventObject;
+import java.util.Objects;
+
+/**
+ * Superclass of conflict events.
+ */
+public abstract class AbstractConflictsEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final ConflictCollection conflicts;
+
+    /**
+     * Constructs a new {@code ExpertModeChangedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param conflicts collection of all conflicts
+     */
+    AbstractConflictsEvent(Object source, ConflictCollection conflicts) {
+        super(source);
+        this.conflicts = Objects.requireNonNull(conflicts);
+    }
+
+    /**
+     * Returns collection of all conflicts.
+     * @return collection of all conflicts
+     */
+    public ConflictCollection getConflicts() {
+        return conflicts;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/conflict/ConflictsAddedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/conflict/ConflictsAddedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/conflict/ConflictsAddedEvent.java	(revision 34000)
@@ -0,0 +1,29 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.conflict;
+
+import java.util.Collection;
+
+/**
+ * Event fired when conflicts are added.
+ */
+public class ConflictsAddedEvent extends AbstractConflictsEvent {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code ConflictsAddedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param conflicts conflicts collection
+     */
+    public ConflictsAddedEvent(Object source, ConflictCollection conflicts) {
+        super(source, conflicts);
+    }
+
+    /**
+     * Returns added conflicts.
+     * @return added conflicts
+     */
+    public Collection<Conflict<?>> getAddedConflicts() {
+        throw new UnsupportedOperationException("Requires a change in core"); // FIXME
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/conflict/ConflictsRemovedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/conflict/ConflictsRemovedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/conflict/ConflictsRemovedEvent.java	(revision 34000)
@@ -0,0 +1,29 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.conflict;
+
+import java.util.Collection;
+
+/**
+ * Event fired when conflicts are removed.
+ */
+public class ConflictsRemovedEvent extends AbstractConflictsEvent {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code ConflictsRemovedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param conflicts conflicts collection
+     */
+    public ConflictsRemovedEvent(Object source, ConflictCollection conflicts) {
+        super(source, conflicts);
+    }
+
+    /**
+     * Returns removed conflicts.
+     * @return removed conflicts
+     */
+    public Collection<Conflict<?>> getRemovedConflicts() {
+        throw new UnsupportedOperationException("Requires a change in core"); // FIXME
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/AbstractNoteDataEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/AbstractNoteDataEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/AbstractNoteDataEvent.java	(revision 34000)
@@ -0,0 +1,33 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+import java.util.EventObject;
+import java.util.Objects;
+
+/**
+ * Superclass of note events.
+ */
+abstract class AbstractNoteDataEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final NoteData noteData;
+
+    /**
+     * Constructs a new {@code AbstractNoteDataEvent}.
+     * @param source object on which the Event initially occurred
+     * @param noteData note data set
+     */
+    AbstractNoteDataEvent(Object source, NoteData noteData) {
+        super(source);
+        this.noteData = Objects.requireNonNull(noteData);
+    }
+
+    /**
+     * Returns note data set.
+     * @return note data set
+     */
+    public final NoteData getNoteData() {
+        return noteData;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/NoteDataUpdatedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/NoteDataUpdatedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/NoteDataUpdatedEvent.java	(revision 34000)
@@ -0,0 +1,19 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+/**
+ * Event fired when note data set is updated.
+ */
+public class NoteDataUpdatedEvent extends AbstractNoteDataEvent {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code NoteDataUpdatedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param noteData note data set
+     */
+    public NoteDataUpdatedEvent(Object source, NoteData noteData) {
+        super(source, noteData);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/SelectedNoteChangedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/SelectedNoteChangedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/SelectedNoteChangedEvent.java	(revision 34000)
@@ -0,0 +1,19 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm;
+
+/**
+ * Event fired when the selected note changes.
+ */
+public class SelectedNoteChangedEvent extends AbstractNoteDataEvent {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code SelectedNoteChangedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param noteData note data set
+     */
+    public SelectedNoteChangedEvent(Object source, NoteData noteData) {
+        super(source, noteData);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/history/AbstractHistoryEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/history/AbstractHistoryEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/history/AbstractHistoryEvent.java	(revision 34000)
@@ -0,0 +1,33 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm.history;
+
+import java.util.EventObject;
+import java.util.Objects;
+
+/**
+ * Superclass of history events.
+ */
+public class AbstractHistoryEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final HistoryDataSet historyDataSet;
+
+    /**
+     * Constructs a new {@code AbstractHistoryEvent}.
+     * @param source object on which the Event initially occurred
+     * @param historyDataSet history data set for which the event is trigerred
+     */
+    AbstractHistoryEvent(Object source, HistoryDataSet historyDataSet) {
+        super(source);
+        this.historyDataSet = Objects.requireNonNull(historyDataSet);
+    }
+
+    /**
+     * Returns history data set for which the event is trigerred.
+     * @return history data set for which the event is trigerred
+     */
+    public final HistoryDataSet getHistoryDataSet() {
+        return historyDataSet;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/history/HistoryClearedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/history/HistoryClearedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/history/HistoryClearedEvent.java	(revision 34000)
@@ -0,0 +1,19 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm.history;
+
+/**
+ * Event fired when history is cleared.
+ */
+public class HistoryClearedEvent extends AbstractHistoryEvent {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code HistoryClearedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param historyDataSet history data set for which the event is trigerred
+     */
+    public HistoryClearedEvent(Object source, HistoryDataSet historyDataSet) {
+        super(source, historyDataSet);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/history/HistoryUpdatedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/history/HistoryUpdatedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/osm/history/HistoryUpdatedEvent.java	(revision 34000)
@@ -0,0 +1,33 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.osm.history;
+
+import org.openstreetmap.josm.data.osm.PrimitiveId;
+
+/**
+ * Event fired when history is updated for an OSM primitive.
+ */
+public class HistoryUpdatedEvent extends AbstractHistoryEvent {
+
+    private static final long serialVersionUID = 1L;
+
+    private final PrimitiveId id;
+
+    /**
+     * Constructs a new {@code HistoryUpdatedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param historyDataSet history data set for which the event is trigerred
+     * @param id the primitive id for which history has been updated
+     */
+    public HistoryUpdatedEvent(Object source, HistoryDataSet historyDataSet, PrimitiveId id) {
+        super(source, historyDataSet);
+        this.id = id;
+    }
+
+    /**
+     * Returns the primitive id for which history has been updated.
+     * @return primitive id for which history has been updated
+     */
+    public PrimitiveId getId() {
+        return id;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/projection/ProjectionChangedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/projection/ProjectionChangedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/data/projection/ProjectionChangedEvent.java	(revision 34000)
@@ -0,0 +1,44 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.data.projection;
+
+import java.util.EventObject;
+import java.util.Objects;
+
+/**
+ * Event fired when the map projection changes.
+ */
+public class ProjectionChangedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final Projection oldValue;
+    private final Projection newValue;
+
+    /**
+     * Constructs a new {@code ProjectionChangedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param oldValue old projection
+     * @param newValue new projection
+     */
+    public ProjectionChangedEvent(Object source, Projection oldValue, Projection newValue) {
+        super(source);
+        this.oldValue = Objects.requireNonNull(oldValue);
+        this.newValue = Objects.requireNonNull(newValue);
+    }
+
+    /**
+     * Returns the old projection.
+     * @return the old projection
+     */
+    public final Projection getOldValue() {
+        return oldValue;
+    }
+
+    /**
+     * Returns the new projection.
+     * @return the new projection
+     */
+    public final Projection getNewValue() {
+        return newValue;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/AllowConcurrentEvents.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/AllowConcurrentEvents.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/AllowConcurrentEvents.java	(revision 34000)
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks an event subscriber method as being thread-safe. This annotation indicates that EventBus
+ * may invoke the event subscriber simultaneously from multiple threads.
+ *
+ * <p>This does not mark the method, and so should be used in combination with {@link Subscribe}.
+ *
+ * @author Cliff Biffle
+ * @since 10.0
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface AllowConcurrentEvents {}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/AsyncEventBus.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/AsyncEventBus.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/AsyncEventBus.java	(revision 34000)
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2007 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import java.util.concurrent.Executor;
+
+/**
+ * An {@link EventBus} that takes the Executor of your choice and uses it to dispatch events,
+ * allowing dispatch to occur asynchronously.
+ *
+ * @author Cliff Biffle
+ * @since 10.0
+ */
+public class AsyncEventBus extends EventBus {
+
+  /**
+   * Creates a new AsyncEventBus that will use {@code executor} to dispatch events. Assigns {@code
+   * identifier} as the bus's name for logging purposes.
+   *
+   * @param identifier short name for the bus, for logging purposes.
+   * @param executor Executor to use to dispatch events. It is the caller's responsibility to shut
+   *     down the executor after the last event has been posted to this event bus.
+   */
+  public AsyncEventBus(String identifier, Executor executor) {
+    super(identifier, executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
+  }
+
+  /**
+   * Creates a new AsyncEventBus that will use {@code executor} to dispatch events.
+   *
+   * @param executor Executor to use to dispatch events. It is the caller's responsibility to shut
+   *     down the executor after the last event has been posted to this event bus.
+   * @param subscriberExceptionHandler Handler used to handle exceptions thrown from subscribers.
+   *     See {@link SubscriberExceptionHandler} for more information.
+   * @since 16.0
+   */
+  public AsyncEventBus(Executor executor, SubscriberExceptionHandler subscriberExceptionHandler) {
+    super("default", executor, Dispatcher.legacyAsync(), subscriberExceptionHandler);
+  }
+
+  /**
+   * Creates a new AsyncEventBus that will use {@code executor} to dispatch events.
+   *
+   * @param executor Executor to use to dispatch events. It is the caller's responsibility to shut
+   *     down the executor after the last event has been posted to this event bus.
+   */
+  public AsyncEventBus(Executor executor) {
+    super("default", executor, Dispatcher.legacyAsync(), LoggingHandler.INSTANCE);
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/DeadEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/DeadEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/DeadEvent.java	(revision 34000)
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2007 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import java.util.Objects;
+
+/**
+ * Wraps an event that was posted, but which had no subscribers and thus could not be delivered.
+ *
+ * <p>Registering a DeadEvent subscriber is useful for debugging or logging, as it can detect
+ * misconfigurations in a system's event distribution.
+ *
+ * @author Cliff Biffle
+ * @since 10.0
+ */
+public class DeadEvent {
+
+  private final Object source;
+  private final Object event;
+
+  /**
+   * Creates a new DeadEvent.
+   *
+   * @param source object broadcasting the DeadEvent (generally the {@link EventBus}).
+   * @param event the event that could not be delivered.
+   */
+  public DeadEvent(Object source, Object event) {
+    this.source = Objects.requireNonNull(source);
+    this.event = Objects.requireNonNull(event);
+  }
+
+  /**
+   * Returns the object that originated this event (<em>not</em> the object that originated the
+   * wrapped event). This is generally an {@link EventBus}.
+   *
+   * @return the source of this event.
+   */
+  public Object getSource() {
+    return source;
+  }
+
+  /**
+   * Returns the wrapped, 'dead' event, which the system was unable to deliver to any registered
+   * subscriber.
+   *
+   * @return the 'dead' event that could not be delivered.
+   */
+  public Object getEvent() {
+    return event;
+  }
+
+  @Override
+  public String toString() {
+    return "DeadEvent [source=" + source + ", event=" + event + ']';
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/Dispatcher.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/Dispatcher.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/Dispatcher.java	(revision 34000)
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2014 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * Handler for dispatching events to subscribers, providing different event ordering guarantees that
+ * make sense for different situations.
+ *
+ * <p><b>Note:</b> The dispatcher is orthogonal to the subscriber's {@code Executor}. The dispatcher
+ * controls the order in which events are dispatched, while the executor controls how (i.e. on which
+ * thread) the subscriber is actually called when an event is dispatched to it.
+ *
+ * @author Colin Decker
+ */
+abstract class Dispatcher {
+
+  /**
+   * Returns a dispatcher that queues events that are posted reentrantly on a thread that is already
+   * dispatching an event, guaranteeing that all events posted on a single thread are dispatched to
+   * all subscribers in the order they are posted.
+   *
+   * <p>When all subscribers are dispatched to using a <i>direct</i> executor (which dispatches on
+   * the same thread that posts the event), this yields a breadth-first dispatch order on each
+   * thread. That is, all subscribers to a single event A will be called before any subscribers to
+   * any events B and C that are posted to the event bus by the subscribers to A.
+   */
+  static Dispatcher perThreadDispatchQueue() {
+    return new PerThreadQueuedDispatcher();
+  }
+
+  /**
+   * Returns a dispatcher that queues events that are posted in a single global queue. This behavior
+   * matches the original behavior of AsyncEventBus exactly, but is otherwise not especially useful.
+   * For async dispatch, an {@linkplain #immediate() immediate} dispatcher should generally be
+   * preferable.
+   */
+  static Dispatcher legacyAsync() {
+    return new LegacyAsyncDispatcher();
+  }
+
+  /**
+   * Returns a dispatcher that dispatches events to subscribers immediately as they're posted
+   * without using an intermediate queue to change the dispatch order. This is effectively a
+   * depth-first dispatch order, vs. breadth-first when using a queue.
+   */
+  static Dispatcher immediate() {
+    return ImmediateDispatcher.INSTANCE;
+  }
+
+  /** Dispatches the given {@code event} to the given {@code subscribers}. */
+  abstract void dispatch(Object event, Iterator<Subscriber> subscribers);
+
+  /** Implementation of a {@link #perThreadDispatchQueue()} dispatcher. */
+  private static final class PerThreadQueuedDispatcher extends Dispatcher {
+
+    // This dispatcher matches the original dispatch behavior of EventBus.
+
+    /** Per-thread queue of events to dispatch. */
+    private final ThreadLocal<Queue<Event>> queue =
+        new ThreadLocal<Queue<Event>>() {
+          @Override
+          protected Queue<Event> initialValue() {
+            return new ArrayDeque<>();
+          }
+        };
+
+    /** Per-thread dispatch state, used to avoid reentrant event dispatching. */
+    private final ThreadLocal<Boolean> dispatching =
+        new ThreadLocal<Boolean>() {
+          @Override
+          protected Boolean initialValue() {
+            return false;
+          }
+        };
+
+    @Override
+    void dispatch(Object event, Iterator<Subscriber> subscribers) {
+      Objects.requireNonNull(event);
+      Objects.requireNonNull(subscribers);
+      Queue<Event> queueForThread = queue.get();
+      queueForThread.offer(new Event(event, subscribers));
+
+      if (!dispatching.get()) {
+        dispatching.set(true);
+        try {
+          Event nextEvent;
+          while ((nextEvent = queueForThread.poll()) != null) {
+            while (nextEvent.subscribers.hasNext()) {
+              nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
+            }
+          }
+        } finally {
+          dispatching.remove();
+          queue.remove();
+        }
+      }
+    }
+
+    private static final class Event {
+      private final Object event;
+      private final Iterator<Subscriber> subscribers;
+
+      private Event(Object event, Iterator<Subscriber> subscribers) {
+        this.event = event;
+        this.subscribers = subscribers;
+      }
+    }
+  }
+
+  /** Implementation of a {@link #legacyAsync()} dispatcher. */
+  private static final class LegacyAsyncDispatcher extends Dispatcher {
+
+    // This dispatcher matches the original dispatch behavior of AsyncEventBus.
+    //
+    // We can't really make any guarantees about the overall dispatch order for this dispatcher in
+    // a multithreaded environment for a couple reasons:
+    //
+    // 1. Subscribers to events posted on different threads can be interleaved with each other
+    //    freely. (A event on one thread, B event on another could yield any of
+    //    [a1, a2, a3, b1, b2], [a1, b2, a2, a3, b2], [a1, b2, b3, a2, a3], etc.)
+    // 2. It's possible for subscribers to actually be dispatched to in a different order than they
+    //    were added to the queue. It's easily possible for one thread to take the head of the
+    //    queue, immediately followed by another thread taking the next element in the queue. That
+    //    second thread can then dispatch to the subscriber it took before the first thread does.
+    //
+    // All this makes me really wonder if there's any value in queueing here at all. A dispatcher
+    // that simply loops through the subscribers and dispatches the event to each would actually
+    // probably provide a stronger order guarantee, though that order would obviously be different
+    // in some cases.
+
+    /** Global event queue. */
+    private final ConcurrentLinkedQueue<EventWithSubscriber> queue =
+        new ConcurrentLinkedQueue<>();
+
+    @Override
+    void dispatch(Object event, Iterator<Subscriber> subscribers) {
+      Objects.requireNonNull(event);
+      while (subscribers.hasNext()) {
+        queue.add(new EventWithSubscriber(event, subscribers.next()));
+      }
+
+      EventWithSubscriber e;
+      while ((e = queue.poll()) != null) {
+        e.subscriber.dispatchEvent(e.event);
+      }
+    }
+
+    private static final class EventWithSubscriber {
+      private final Object event;
+      private final Subscriber subscriber;
+
+      private EventWithSubscriber(Object event, Subscriber subscriber) {
+        this.event = event;
+        this.subscriber = subscriber;
+      }
+    }
+  }
+
+  /** Implementation of {@link #immediate()}. */
+  private static final class ImmediateDispatcher extends Dispatcher {
+    private static final ImmediateDispatcher INSTANCE = new ImmediateDispatcher();
+
+    @Override
+    void dispatch(Object event, Iterator<Subscriber> subscribers) {
+      Objects.requireNonNull(event);
+      while (subscribers.hasNext()) {
+        subscribers.next().dispatchEvent(event);
+      }
+    }
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/EventBus.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/EventBus.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/EventBus.java	(revision 34000)
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2007 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Dispatches events to listeners, and provides ways for listeners to register themselves.
+ *
+ * <p>The EventBus allows publish-subscribe-style communication between components without requiring
+ * the components to explicitly register with one another (and thus be aware of each other). It is
+ * designed exclusively to replace traditional Java in-process event distribution using explicit
+ * registration. It is <em>not</em> a general-purpose publish-subscribe system, nor is it intended
+ * for interprocess communication.
+ *
+ * <h2>Receiving Events</h2>
+ *
+ * <p>To receive events, an object should:
+ *
+ * <ol>
+ *   <li>Expose a public method, known as the <i>event subscriber</i>, which accepts a single
+ *       argument of the type of event desired;
+ *   <li>Mark it with a {@link Subscribe} annotation;
+ *   <li>Pass itself to an EventBus instance's {@link #register(Object)} method.
+ * </ol>
+ *
+ * <h2>Posting Events</h2>
+ *
+ * <p>To post an event, simply provide the event object to the {@link #post(Object)} method. The
+ * EventBus instance will determine the type of event and route it to all registered listeners.
+ *
+ * <p>Events are routed based on their type &mdash; an event will be delivered to any subscriber for
+ * any type to which the event is <em>assignable.</em> This includes implemented interfaces, all
+ * superclasses, and all interfaces implemented by superclasses.
+ *
+ * <p>When {@code post} is called, all registered subscribers for an event are run in sequence, so
+ * subscribers should be reasonably quick. If an event may trigger an extended process (such as a
+ * database load), spawn a thread or queue it for later. (For a convenient way to do this, use an
+ * {@link AsyncEventBus}.)
+ *
+ * <h2>Subscriber Methods</h2>
+ *
+ * <p>Event subscriber methods must accept only one argument: the event.
+ *
+ * <p>Subscribers should not, in general, throw. If they do, the EventBus will catch and log the
+ * exception. This is rarely the right solution for error handling and should not be relied upon; it
+ * is intended solely to help find problems during development.
+ *
+ * <p>The EventBus guarantees that it will not call a subscriber method from multiple threads
+ * simultaneously, unless the method explicitly allows it by bearing the {@link
+ * AllowConcurrentEvents} annotation. If this annotation is not present, subscriber methods need not
+ * worry about being reentrant, unless also called from outside the EventBus.
+ *
+ * <h2>Dead Events</h2>
+ *
+ * <p>If an event is posted, but no registered subscribers can accept it, it is considered "dead."
+ * To give the system a second chance to handle dead events, they are wrapped in an instance of
+ * {@link DeadEvent} and reposted.
+ *
+ * <p>If a subscriber for a supertype of all events (such as Object) is registered, no event will
+ * ever be considered dead, and no DeadEvents will be generated. Accordingly, while DeadEvent
+ * extends {@link Object}, a subscriber registered to receive any Object will never receive a
+ * DeadEvent.
+ *
+ * <p>This class is safe for concurrent use.
+ *
+ * <p>See the Guava User Guide article on <a
+ * href="https://github.com/google/guava/wiki/EventBusExplained">{@code EventBus}</a>.
+ *
+ * @author Cliff Biffle
+ * @since 10.0
+ */
+public class EventBus {
+
+  private static final Logger logger = Logger.getLogger(EventBus.class.getName());
+
+  private final String identifier;
+  private final Executor executor;
+  private final SubscriberExceptionHandler exceptionHandler;
+
+  private final SubscriberRegistry subscribers = new SubscriberRegistry(this);
+  private final Dispatcher dispatcher;
+
+  enum DirectExecutor implements Executor {
+    INSTANCE;
+
+    @Override
+    public void execute(Runnable command) {
+      command.run();
+    }
+  }
+
+  /** Creates a new EventBus named "default". */
+  public EventBus() {
+    this("default");
+  }
+
+  /**
+   * Creates a new EventBus with the given {@code identifier}.
+   *
+   * @param identifier a brief name for this bus, for logging purposes. Should be a valid Java
+   *     identifier.
+   */
+  public EventBus(String identifier) {
+    this(
+        identifier,
+        DirectExecutor.INSTANCE,
+        Dispatcher.perThreadDispatchQueue(),
+        LoggingHandler.INSTANCE);
+  }
+
+  /**
+   * Creates a new EventBus with the given {@link SubscriberExceptionHandler}.
+   *
+   * @param exceptionHandler Handler for subscriber exceptions.
+   * @since 16.0
+   */
+  public EventBus(SubscriberExceptionHandler exceptionHandler) {
+    this(
+        "default",
+        DirectExecutor.INSTANCE,
+        Dispatcher.perThreadDispatchQueue(),
+        exceptionHandler);
+  }
+
+  EventBus(
+      String identifier,
+      Executor executor,
+      Dispatcher dispatcher,
+      SubscriberExceptionHandler exceptionHandler) {
+    this.identifier = Objects.requireNonNull(identifier);
+    this.executor = Objects.requireNonNull(executor);
+    this.dispatcher = Objects.requireNonNull(dispatcher);
+    this.exceptionHandler = Objects.requireNonNull(exceptionHandler);
+  }
+
+  /**
+   * Returns the identifier for this event bus.
+   * @return the identifier for this event bus
+   *
+   * @since 19.0
+   */
+  public final String identifier() {
+    return identifier;
+  }
+
+  /** Returns the default executor this event bus uses for dispatching events to subscribers. */
+  final Executor executor() {
+    return executor;
+  }
+
+  /** Handles the given exception thrown by a subscriber with the given context. */
+  void handleSubscriberException(Throwable e, SubscriberExceptionContext context) {
+    Objects.requireNonNull(e);
+    Objects.requireNonNull(context);
+    try {
+      exceptionHandler.handleException(e, context);
+    } catch (Throwable e2) {
+      // if the handler threw an exception... well, just log it
+      logger.log(
+          Level.SEVERE,
+          String.format(Locale.ROOT, "Exception %s thrown while handling exception: %s", e2, e),
+          e2);
+    }
+  }
+
+  /**
+   * Registers all subscriber methods on {@code object} to receive events.
+   *
+   * @param object object whose subscriber methods should be registered.
+   */
+  public void register(Object object) {
+    subscribers.register(object);
+  }
+
+  /**
+   * Unregisters all subscriber methods on a registered {@code object}.
+   *
+   * @param object object whose subscriber methods should be unregistered.
+   * @throws IllegalArgumentException if the object was not previously registered.
+   */
+  public void unregister(Object object) {
+    subscribers.unregister(object);
+  }
+
+  /**
+   * Posts an event to all registered subscribers. This method will return successfully after the
+   * event has been posted to all subscribers, and regardless of any exceptions thrown by
+   * subscribers.
+   *
+   * <p>If no subscribers have been subscribed for {@code event}'s class, and {@code event} is not
+   * already a {@link DeadEvent}, it will be wrapped in a DeadEvent and reposted.
+   *
+   * @param event event to post.
+   */
+  public void post(Object event) {
+    Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
+    if (eventSubscribers.hasNext()) {
+      dispatcher.dispatch(event, eventSubscribers);
+    } else if (!(event instanceof DeadEvent)) {
+      // the event had no subscribers and was not itself a DeadEvent
+      post(new DeadEvent(this, event));
+    }
+  }
+
+  @Override
+  public String toString() {
+    return "EventBus [" + identifier + ']';
+  }
+
+  /** Simple logging handler for subscriber exceptions. */
+  static final class LoggingHandler implements SubscriberExceptionHandler {
+    static final LoggingHandler INSTANCE = new LoggingHandler();
+
+    @Override
+    public void handleException(Throwable exception, SubscriberExceptionContext context) {
+      Logger logger = logger(context);
+      if (logger.isLoggable(Level.SEVERE)) {
+        logger.log(Level.SEVERE, message(context), exception);
+      }
+    }
+
+    private static Logger logger(SubscriberExceptionContext context) {
+      return Logger.getLogger(EventBus.class.getName() + "." + context.getEventBus().identifier());
+    }
+
+    private static String message(SubscriberExceptionContext context) {
+      Method method = context.getSubscriberMethod();
+      return "Exception thrown by subscriber method "
+          + method.getName()
+          + '('
+          + method.getParameterTypes()[0].getName()
+          + ')'
+          + " on subscriber "
+          + context.getSubscriber()
+          + " when dispatching event: "
+          + context.getEvent();
+    }
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/JosmEventBus.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/JosmEventBus.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/JosmEventBus.java	(revision 34000)
@@ -0,0 +1,64 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.eventbus;
+
+import java.util.logging.Level;
+
+import org.openstreetmap.josm.eventbus.EventBus.DirectExecutor;
+import org.openstreetmap.josm.tools.Logging;
+
+/**
+ * The unique JOSM event bus.
+ */
+public class JosmEventBus {
+
+    private static final EventBus BUS = new EventBus("JOSM", DirectExecutor.INSTANCE, Dispatcher.perThreadDispatchQueue(),
+            (exception, context) -> Logging.logWithStackTrace(Level.SEVERE, exception, "Event bus error in {0}:", context));
+
+    private JosmEventBus() {
+        // Hide default constructor
+    }
+
+    /**
+     * Returns the unique JOSM event bus.
+     * @return the unique JOSM event bus
+     */
+    public static EventBus getBus() {
+        return BUS;
+    }
+    
+    /**
+     * Registers all subscriber methods on {@code object} to receive events.
+     *
+     * @param object object whose subscriber methods should be registered.
+     * @see EventBus#register
+     */
+    public static void register(Object object) {
+        BUS.register(object);
+    }
+
+    /**
+     * Unregisters all subscriber methods on a registered {@code object}.
+     *
+     * @param object object whose subscriber methods should be unregistered.
+     * @throws IllegalArgumentException if the object was not previously registered.
+     * @see EventBus#unregister
+     */
+    public static void unregister(Object object) {
+        BUS.unregister(object);
+    }
+
+    /**
+     * Posts an event to all registered subscribers. This method will return successfully after the
+     * event has been posted to all subscribers, and regardless of any exceptions thrown by
+     * subscribers.
+     *
+     * <p>If no subscribers have been subscribed for {@code event}'s class, and {@code event} is not
+     * already a {@link DeadEvent}, it will be wrapped in a DeadEvent and reposted.
+     *
+     * @param event event to post.
+     * @see EventBus#post
+     */
+    public static void post(Object event) {
+        BUS.post(event);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/Subscribe.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/Subscribe.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/Subscribe.java	(revision 34000)
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a method as an event subscriber.
+ *
+ * <p>The type of event will be indicated by the method's first (and only) parameter. If this
+ * annotation is applied to methods with zero parameters, or more than one parameter, the object
+ * containing the method will not be able to register for event delivery from the {@link EventBus}.
+ *
+ * <p>Unless also annotated with @{@link AllowConcurrentEvents}, event subscriber methods will be
+ * invoked serially by each event bus that they are registered with.
+ *
+ * @author Cliff Biffle
+ * @since 10.0
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Subscribe {}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/Subscriber.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/Subscriber.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/Subscriber.java	(revision 34000)
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2014 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * A subscriber method on a specific object, plus the executor that should be used for dispatching
+ * events to it.
+ *
+ * <p>Two subscribers are equivalent when they refer to the same method on the same object (not
+ * class). This property is used to ensure that no subscriber method is registered more than once.
+ *
+ * @author Colin Decker
+ */
+class Subscriber {
+
+  /** Creates a {@code Subscriber} for {@code method} on {@code listener}. */
+  static Subscriber create(EventBus bus, Object listener, Method method) {
+    return isDeclaredThreadSafe(method)
+        ? new Subscriber(bus, listener, method)
+        : new SynchronizedSubscriber(bus, listener, method);
+  }
+
+  /** The event bus this subscriber belongs to. */
+  private EventBus bus;
+
+  /** The object with the subscriber method. */
+  final Object target;
+
+  /** Subscriber method. */
+  private final Method method;
+
+  /** Executor to use for dispatching events to this subscriber. */
+  private final Executor executor;
+
+  private Subscriber(EventBus bus, Object target, Method method) {
+    this.bus = bus;
+    this.target = Objects.requireNonNull(target);
+    this.method = method;
+    method.setAccessible(true);
+
+    this.executor = bus.executor();
+  }
+
+  /** Dispatches {@code event} to this subscriber using the proper executor. */
+  final void dispatchEvent(final Object event) {
+    executor.execute(
+        new Runnable() {
+          @Override
+          public void run() {
+            try {
+              invokeSubscriberMethod(event);
+            } catch (InvocationTargetException e) {
+              bus.handleSubscriberException(e.getCause(), context(event));
+            }
+          }
+        });
+  }
+
+  /**
+   * Invokes the subscriber method. This method can be overridden to make the invocation
+   * synchronized.
+   */
+  void invokeSubscriberMethod(Object event) throws InvocationTargetException {
+    try {
+      method.invoke(target, Objects.requireNonNull(event));
+    } catch (IllegalArgumentException e) {
+      throw new Error("Method rejected target/argument: " + event, e);
+    } catch (IllegalAccessException e) {
+      throw new Error("Method became inaccessible: " + event, e);
+    } catch (InvocationTargetException e) {
+      if (e.getCause() instanceof Error) {
+        throw (Error) e.getCause();
+      }
+      throw e;
+    }
+  }
+
+  /** Gets the context for the given event. */
+  private SubscriberExceptionContext context(Object event) {
+    return new SubscriberExceptionContext(bus, event, target, method);
+  }
+
+  @Override
+  public final int hashCode() {
+    return (31 + method.hashCode()) * 31 + System.identityHashCode(target);
+  }
+
+  @Override
+  public final boolean equals(Object obj) {
+    if (obj instanceof Subscriber) {
+      Subscriber that = (Subscriber) obj;
+      // Use == so that different equal instances will still receive events.
+      // We only guard against the case that the same object is registered
+      // multiple times
+      return target == that.target && method.equals(that.method);
+    }
+    return false;
+  }
+
+  /**
+   * Checks whether {@code method} is thread-safe, as indicated by the presence of the {@link
+   * AllowConcurrentEvents} annotation.
+   */
+  private static boolean isDeclaredThreadSafe(Method method) {
+    return method.getAnnotation(AllowConcurrentEvents.class) != null;
+  }
+
+  /**
+   * Subscriber that synchronizes invocations of a method to ensure that only one thread may enter
+   * the method at a time.
+   */
+  static final class SynchronizedSubscriber extends Subscriber {
+
+    private SynchronizedSubscriber(EventBus bus, Object target, Method method) {
+      super(bus, target, method);
+    }
+
+    @Override
+    void invokeSubscriberMethod(Object event) throws InvocationTargetException {
+      synchronized (this) {
+        super.invokeSubscriberMethod(event);
+      }
+    }
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/SubscriberExceptionContext.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/SubscriberExceptionContext.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/SubscriberExceptionContext.java	(revision 34000)
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2013 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import java.lang.reflect.Method;
+import java.util.Objects;
+
+/**
+ * Context for an exception thrown by a subscriber.
+ *
+ * @since 16.0
+ */
+public class SubscriberExceptionContext {
+  private final EventBus eventBus;
+  private final Object event;
+  private final Object subscriber;
+  private final Method subscriberMethod;
+
+  /**
+   * @param eventBus The {@link EventBus} that handled the event and the subscriber. Useful for
+   *     broadcasting a a new event based on the error.
+   * @param event The event object that caused the subscriber to throw.
+   * @param subscriber The source subscriber context.
+   * @param subscriberMethod the subscribed method.
+   */
+  SubscriberExceptionContext(
+      EventBus eventBus, Object event, Object subscriber, Method subscriberMethod) {
+    this.eventBus = Objects.requireNonNull(eventBus);
+    this.event = Objects.requireNonNull(event);
+    this.subscriber = Objects.requireNonNull(subscriber);
+    this.subscriberMethod = Objects.requireNonNull(subscriberMethod);
+  }
+
+  /**
+   * @return The {@link EventBus} that handled the event and the subscriber. Useful for broadcasting
+   *     a a new event based on the error.
+   */
+  public EventBus getEventBus() {
+    return eventBus;
+  }
+
+  /** @return The event object that caused the subscriber to throw. */
+  public Object getEvent() {
+    return event;
+  }
+
+  /** @return The object context that the subscriber was called on. */
+  public Object getSubscriber() {
+    return subscriber;
+  }
+
+  /** @return The subscribed method that threw the exception. */
+  public Method getSubscriberMethod() {
+    return subscriberMethod;
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/SubscriberExceptionHandler.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/SubscriberExceptionHandler.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/SubscriberExceptionHandler.java	(revision 34000)
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2013 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+/**
+ * Handler for exceptions thrown by event subscribers.
+ *
+ * @since 16.0
+ */
+public interface SubscriberExceptionHandler {
+  /**
+   * Handles exceptions thrown by subscribers.
+   * @param exception exception
+   * @param context context
+   */
+  void handleException(Throwable exception, SubscriberExceptionContext context);
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/SubscriberRegistry.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/SubscriberRegistry.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/SubscriberRegistry.java	(revision 34000)
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2014 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.stream.Collectors;
+
+import org.openstreetmap.josm.tools.MultiMap;
+
+/**
+ * Registry of subscribers to a single event bus.
+ *
+ * @author Colin Decker
+ */
+final class SubscriberRegistry {
+
+  /**
+   * All registered subscribers, indexed by event type.
+   *
+   * <p>The {@link CopyOnWriteArraySet} values make it easy and relatively lightweight to get an
+   * immutable snapshot of all current subscribers to an event without any locking.
+   */
+  private final ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers =
+    new ConcurrentHashMap<>();
+
+  /** The event bus this registry belongs to. */
+  private final EventBus bus;
+
+  SubscriberRegistry(EventBus bus) {
+    this.bus = Objects.requireNonNull(bus);
+  }
+
+  /** Registers all subscriber methods on the given listener object. */
+  void register(Object listener) {
+    MultiMap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);
+
+    for (Entry<Class<?>, Set<Subscriber>> entry : listenerMethods.entrySet()) {
+      Class<?> eventType = entry.getKey();
+      Collection<Subscriber> eventMethodsInListener = entry.getValue();
+
+      CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);
+
+      if (eventSubscribers == null) {
+        CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<>();
+        eventSubscribers =
+            firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
+      }
+
+      eventSubscribers.addAll(eventMethodsInListener);
+    }
+  }
+
+  /** Unregisters all subscribers on the given listener object. */
+  void unregister(Object listener) {
+    MultiMap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);
+
+    for (Entry<Class<?>, Set<Subscriber>> entry : listenerMethods.entrySet()) {
+      Class<?> eventType = entry.getKey();
+      Collection<Subscriber> listenerMethodsForType = entry.getValue();
+
+      CopyOnWriteArraySet<Subscriber> currentSubscribers = subscribers.get(eventType);
+      if (currentSubscribers == null || !currentSubscribers.removeAll(listenerMethodsForType)) {
+        // if removeAll returns true, all we really know is that at least one subscriber was
+        // removed... however, barring something very strange we can assume that if at least one
+        // subscriber was removed, all subscribers on listener for that event type were... after
+        // all, the definition of subscribers on a particular class is totally static
+        throw new IllegalArgumentException(
+            "missing event subscriber for an annotated method. Is " + listener + " registered?");
+      }
+
+      // don't try to remove the set if it's empty; that can't be done safely without a lock
+      // anyway, if the set is empty it'll just be wrapping an array of length 0
+    }
+  }
+
+  Set<Subscriber> getSubscribersForTesting(Class<?> eventType) {
+    return firstNonNull(subscribers.get(eventType), new HashSet<Subscriber>());
+  }
+
+  /**
+   * Gets an iterator representing an immutable snapshot of all subscribers to the given event at
+   * the time this method is called.
+   */
+  Iterator<Subscriber> getSubscribers(Object event) {
+    Set<Class<?>> eventTypes = flattenHierarchy(event.getClass());
+
+    List<Subscriber> subscriberList = new ArrayList<>(eventTypes.size());
+
+    for (Class<?> eventType : eventTypes) {
+      CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);
+      if (eventSubscribers != null) {
+        // eager no-copy snapshot
+        subscriberList.addAll(eventSubscribers);
+      }
+    }
+
+    return Collections.unmodifiableList(subscriberList).iterator();
+  }
+
+  /**
+   * Returns all subscribers for the given listener grouped by the type of event they subscribe to.
+   */
+  private MultiMap<Class<?>, Subscriber> findAllSubscribers(Object listener) {
+    MultiMap<Class<?>, Subscriber> methodsInListener = new MultiMap<>();
+    Class<?> clazz = listener.getClass();
+    for (Method method : getAnnotatedMethods(clazz)) {
+      Class<?>[] parameterTypes = method.getParameterTypes();
+      Class<?> eventType = parameterTypes[0];
+      methodsInListener.put(eventType, Subscriber.create(bus, listener, method));
+    }
+    return methodsInListener;
+  }
+
+  private static List<Method> getAnnotatedMethods(Class<?> clazz) {
+    return getAnnotatedMethodsNotCached(clazz);
+  }
+
+  private static List<Method> getAnnotatedMethodsNotCached(Class<?> clazz) {
+    Set<? extends Class<?>> supertypes = getClassesAndInterfaces(clazz);
+    Map<MethodIdentifier, Method> identifiers = new HashMap<>();
+    for (Class<?> supertype : supertypes) {
+      for (Method method : supertype.getDeclaredMethods()) {
+        if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) {
+          // TODO(cgdecker): Should check for a generic parameter type and error out
+          Class<?>[] parameterTypes = method.getParameterTypes();
+          if (parameterTypes.length != 1) {
+              throw new IllegalArgumentException(String.format(
+                    "Method %s has @Subscribe annotation but has %s parameters."
+                  + "Subscriber methods must have exactly 1 parameter.",
+              method,
+              parameterTypes.length));
+          }
+
+          MethodIdentifier ident = new MethodIdentifier(method);
+          if (!identifiers.containsKey(ident)) {
+            identifiers.put(ident, method);
+          }
+        }
+      }
+    }
+    return new ArrayList<>(identifiers.values());
+  }
+
+  /** Global cache of classes to their flattened hierarchy of supertypes. */
+  private static final Map<Class<?>, Set<Class<?>>> flattenHierarchyCache = new HashMap<>();
+
+  /**
+   * Flattens a class's type hierarchy into a set of {@code Class} objects including all
+   * superclasses (transitively) and all interfaces implemented by these superclasses.
+   */
+  static Set<Class<?>> flattenHierarchy(Class<?> concreteClass) {
+      return flattenHierarchyCache.computeIfAbsent(
+              concreteClass, SubscriberRegistry::getClassesAndInterfaces);
+  }
+
+  private static final class MethodIdentifier {
+
+    private final String name;
+    private final List<Class<?>> parameterTypes;
+
+    MethodIdentifier(Method method) {
+      this.name = method.getName();
+      this.parameterTypes = Arrays.asList(method.getParameterTypes());
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(name, parameterTypes);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof MethodIdentifier) {
+        MethodIdentifier ident = (MethodIdentifier) o;
+        return name.equals(ident.name) && parameterTypes.equals(ident.parameterTypes);
+      }
+      return false;
+    }
+  }
+  
+  /**
+   * Returns the first of two given parameters that is not {@code null}, if either is, or otherwise
+   * throws a {@link NullPointerException}.
+   *
+   * <p>To find the first non-null element in an iterable, use {@code Iterables.find(iterable,
+   * Predicates.notNull())}. For varargs, use {@code Iterables.find(Arrays.asList(a, b, c, ...),
+   * Predicates.notNull())}, static importing as necessary.
+   *
+   * <p><b>Note:</b> if {@code first} is represented as an {@link Optional}, this can be
+   * accomplished with {@link Optional#or(Object) first.or(second)}. That approach also allows for
+   * lazy evaluation of the fallback instance, using {@link Optional#or(Supplier)
+   * first.or(supplier)}.
+   *
+   * @return {@code first} if it is non-null; otherwise {@code second} if it is non-null
+   * @throws NullPointerException if both {@code first} and {@code second} are null
+   * @since 18.0 (since 3.0 as {@code Objects.firstNonNull()}).
+   */
+  static <T> T firstNonNull(T first, T second) {
+    if (first != null) {
+      return first;
+    }
+    if (second != null) {
+      return second;
+    }
+    throw new NullPointerException("Both parameters are null");
+  }
+  
+  private static Set<Class<?>> getClassesAndInterfaces(Class<?> clazz) {
+      Set<Class<?>> result = new HashSet<>();
+      Class<?> c = clazz;
+      while (c != null) {
+          result.add(c);
+        for (Set<Class<?>> interfaces : Arrays.stream(c.getInterfaces()).map(
+                SubscriberRegistry::getClassesAndInterfaces).collect(Collectors.toList())) {
+              result.addAll(interfaces);
+          }
+          c = c.getSuperclass();
+      }
+      return result;
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/package-info.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/package-info.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/eventbus/package-info.java	(revision 34000)
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2007 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+/**
+ * The EventBus allows publish-subscribe-style communication between components without requiring
+ * the components to explicitly register with one another (and thus be aware of each other). It is
+ * designed exclusively to replace traditional Java in-process event distribution using explicit
+ * registration. It is <em>not</em> a general-purpose publish-subscribe system, nor is it intended
+ * for interprocess communication.
+ *
+ * <p>See the Guava User Guide article on <a
+ * href="https://github.com/google/guava/wiki/EventBusExplained">{@code EventBus}</a>.
+ *
+ * <h2>One-Minute Guide</h2>
+ *
+ * <p>Converting an existing EventListener-based system to use the EventBus is easy.
+ *
+ * <h3>For Listeners</h3>
+ *
+ * <p>To listen for a specific flavor of event (say, a CustomerChangeEvent)...
+ *
+ * <ul>
+ *   <li><strong>...in traditional Java events:</strong> implement an interface defined with the
+ *       event &mdash; such as CustomerChangeEventListener.
+ *   <li><strong>...with EventBus:</strong> create a method that accepts CustomerChangeEvent as its
+ *       sole argument, and mark it with the {@link org.openstreetmap.josm.eventbus.Subscribe} annotation.
+ * </ul>
+ *
+ * <p>To register your listener methods with the event producers...
+ *
+ * <ul>
+ *   <li><strong>...in traditional Java events:</strong> pass your object to each producer's {@code
+ *       registerCustomerChangeEventListener} method. These methods are rarely defined in common
+ *       interfaces, so in addition to knowing every possible producer, you must also know its type.
+ *   <li><strong>...with EventBus:</strong> pass your object to the {@link
+ *       org.openstreetmap.josm.eventbus.EventBus#register(Object)} method on an EventBus. You'll need to
+ *       make sure that your object shares an EventBus instance with the event producers.
+ * </ul>
+ *
+ * <p>To listen for a common event supertype (such as EventObject or Object)...
+ *
+ * <ul>
+ *   <li><strong>...in traditional Java events:</strong> not easy.
+ *   <li><strong>...with EventBus:</strong> events are automatically dispatched to listeners of any
+ *       supertype, allowing listeners for interface types or "wildcard listeners" for Object.
+ * </ul>
+ *
+ * <p>To listen for and detect events that were dispatched without listeners...
+ *
+ * <ul>
+ *   <li><strong>...in traditional Java events:</strong> add code to each event-dispatching method
+ *       (perhaps using AOP).
+ *   <li><strong>...with EventBus:</strong> subscribe to {@link
+ *       org.openstreetmap.josm.eventbus.DeadEvent}. The EventBus will notify you of any events that were
+ *       posted but not delivered. (Handy for debugging.)
+ * </ul>
+ *
+ * <h3>For Producers</h3>
+ *
+ * <p>To keep track of listeners to your events...
+ *
+ * <ul>
+ *   <li><strong>...in traditional Java events:</strong> write code to manage a list of listeners to
+ *       your object, including synchronization, or use a utility class like EventListenerList.
+ *   <li><strong>...with EventBus:</strong> EventBus does this for you.
+ * </ul>
+ *
+ * <p>To dispatch an event to listeners...
+ *
+ * <ul>
+ *   <li><strong>...in traditional Java events:</strong> write a method to dispatch events to each
+ *       event listener, including error isolation and (if desired) asynchronicity.
+ *   <li><strong>...with EventBus:</strong> pass the event object to an EventBus's {@link
+ *       org.openstreetmap.josm.eventbus.EventBus#post(Object)} method.
+ * </ul>
+ *
+ * <h2>Glossary</h2>
+ *
+ * <p>The EventBus system and code use the following terms to discuss event distribution:
+ *
+ * <dl>
+ *   <dt>Event
+ *   <dd>Any object that may be <em>posted</em> to a bus.
+ *   <dt>Subscribing
+ *   <dd>The act of registering a <em>listener</em> with an EventBus, so that its <em>subscriber
+ *       methods</em> will receive events.
+ *   <dt>Listener
+ *   <dd>An object that wishes to receive events, by exposing <em>subscriber methods</em>.
+ *   <dt>Subscriber method
+ *   <dd>A public method that the EventBus should use to deliver <em>posted</em> events. Subscriber
+ *       methods are marked by the {@link org.openstreetmap.josm.eventbus.Subscribe} annotation.
+ *   <dt>Posting an event
+ *   <dd>Making the event available to any <em>listeners</em> through the EventBus.
+ * </dl>
+ *
+ * <h2>FAQ</h2>
+ *
+ * <h3>Why must I create my own Event Bus, rather than using a singleton?</h3>
+ *
+ * <p>The Event Bus doesn't specify how you use it; there's nothing stopping your application from
+ * having separate EventBus instances for each component, or using separate instances to separate
+ * events by context or topic. This also makes it trivial to set up and tear down EventBus objects
+ * in your tests.
+ *
+ * <p>Of course, if you'd like to have a process-wide EventBus singleton, there's nothing stopping
+ * you from doing it that way. Simply have your container (such as Guice) create the EventBus as a
+ * singleton at global scope (or stash it in a static field, if you're into that sort of thing).
+ *
+ * <p>In short, the EventBus is not a singleton because we'd rather not make that decision for you.
+ * Use it how you like.
+ *
+ * <h3>Why use an annotation to mark subscriber methods, rather than requiring the listener to
+ * implement an interface?</h3>
+ *
+ * <p>We feel that the Event Bus's {@code @Subscribe} annotation conveys your intentions just as
+ * explicitly as implementing an interface (or perhaps more so), while leaving you free to place
+ * event subscriber methods wherever you wish and give them intention-revealing names.
+ *
+ * <p>Traditional Java Events use a listener interface which typically sports only a handful of
+ * methods -- typically one. This has a number of disadvantages:
+ *
+ * <ul>
+ *   <li>Any one class can only implement a single response to a given event.
+ *   <li>Listener interface methods may conflict.
+ *   <li>The method must be named after the event (e.g. {@code handleChangeEvent}), rather than its
+ *       purpose (e.g. {@code recordChangeInJournal}).
+ *   <li>Each event usually has its own interface, without a common parent interface for a family of
+ *       events (e.g. all UI events).
+ * </ul>
+ *
+ * <p>The difficulties in implementing this cleanly has given rise to a pattern, particularly common
+ * in Swing apps, of using tiny anonymous classes to implement event listener interfaces.
+ *
+ * <p>Compare these two cases:
+ *
+ * <pre>{@code
+ * class ChangeRecorder {
+ *   void setCustomer(Customer cust) {
+ *     cust.addChangeListener(new ChangeListener() {
+ *       void customerChanged(ChangeEvent e) {
+ *         recordChange(e.getChange());
+ *       }
+ *     };
+ *   }
+ * }
+ *
+ * // Class is typically registered by the container.
+ * class EventBusChangeRecorder {
+ *  }{@code @Subscribe void recordCustomerChange(ChangeEvent e) {
+ *     recordChange(e.getChange());
+ *   }
+ * }
+ * }</pre>
+ *
+ * <p>The intent is actually clearer in the second case: there's less noise code, and the event
+ * subscriber has a clear and meaningful name.
+ *
+ * <h3>What about a generic {@code Subscriber<T>} interface?</h3>
+ *
+ * <p>Some have proposed a generic {@code Subscriber<T>} interface for EventBus listeners. This runs
+ * into issues with Java's use of type erasure, not to mention problems in usability.
+ *
+ * <p>Let's say the interface looked something like the following:
+ *
+ * <pre>{@code
+ * interface Subscriber<T> {
+ *   void handleEvent(T event);
+ * }
+ * }</pre>
+ *
+ * <p>Due to erasure, no single class can implement a generic interface more than once with
+ * different type parameters. This is a giant step backwards from traditional Java Events, where
+ * even if {@code actionPerformed} and {@code keyPressed} aren't very meaningful names, at least you
+ * can implement both methods!
+ *
+ * <h3>Doesn't EventBus destroy static typing and eliminate automated refactoring support?</h3>
+ *
+ * <p>Some have freaked out about EventBus's {@code register(Object)} and {@code post(Object)}
+ * methods' use of the {@code Object} type.
+ *
+ * <p>{@code Object} is used here for a good reason: the Event Bus library places no restrictions on
+ * the types of either your event listeners (as in {@code register(Object)}) or the events
+ * themselves (in {@code post(Object)}).
+ *
+ * <p>Event subscriber methods, on the other hand, must explicitly declare their argument type --
+ * the type of event desired (or one of its supertypes). Thus, searching for references to an event
+ * class will instantly find all subscriber methods for that event, and renaming the type will
+ * affect all subscriber methods within view of your IDE (and any code that creates the event).
+ *
+ * <p>It's true that you can rename your {@code @Subscribed} event subscriber methods at will; Event
+ * Bus will not stop this or do anything to propagate the rename because, to Event Bus, the names of
+ * your subscriber methods are irrelevant. Test code that calls the methods directly, of course,
+ * will be affected by your renaming -- but that's what your refactoring tools are for.
+ *
+ * <h3>What happens if I {@code register} a listener without any subscriber methods?</h3>
+ *
+ * <p>Nothing at all.
+ *
+ * <p>The Event Bus was designed to integrate with containers and module systems, with Guice as the
+ * prototypical example. In these cases, it's convenient to have the container/factory/environment
+ * pass <i>every</i> created object to an EventBus's {@code register(Object)} method.
+ *
+ * <p>This way, any object created by the container/factory/environment can hook into the system's
+ * event model simply by exposing subscriber methods.
+ *
+ * <h3>What Event Bus problems can be detected at compile time?</h3>
+ *
+ * <p>Any problem that can be unambiguously detected by Java's type system. For example, defining a
+ * subscriber method for a nonexistent event type.
+ *
+ * <h3>What Event Bus problems can be detected immediately at registration?</h3>
+ *
+ * <p>Immediately upon invoking {@code register(Object)}, the listener being registered is checked
+ * for the <i>well-formedness</i> of its subscriber methods. Specifically, any methods marked with
+ * {@code @Subscribe} must take only a single argument.
+ *
+ * <p>Any violations of this rule will cause an {@code IllegalArgumentException} to be thrown.
+ *
+ * <p>(This check could be moved to compile-time using APT, a solution we're researching.)
+ *
+ * <h3>What Event Bus problems may only be detected later, at runtime?</h3>
+ *
+ * <p>If a component posts events with no registered listeners, it <i>may</i> indicate an error
+ * (typically an indication that you missed a {@code @Subscribe} annotation, or that the listening
+ * component is not loaded).
+ *
+ * <p>(Note that this is <i>not necessarily</i> indicative of a problem. There are many cases where
+ * an application will deliberately ignore a posted event, particularly if the event is coming from
+ * code you don't control.)
+ *
+ * <p>To handle such events, register a subscriber method for the {@code DeadEvent} class. Whenever
+ * EventBus receives an event with no registered subscribers, it will turn it into a {@code
+ * DeadEvent} and pass it your way -- allowing you to log it or otherwise recover.
+ *
+ * <h3>How do I test event listeners and their subscriber methods?</h3>
+ *
+ * <p>Because subscriber methods on your listener classes are normal methods, you can simply call
+ * them from your test code to simulate the EventBus.
+ */
+package org.openstreetmap.josm.eventbus;
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/MapFrameInitializedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/MapFrameInitializedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/MapFrameInitializedEvent.java	(revision 34000)
@@ -0,0 +1,43 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when the map frame is initialized (after the first data is loaded).
+ */
+public class MapFrameInitializedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final MapFrame oldFrame;
+    private final MapFrame newFrame;
+
+    /**
+     * Constructs a new {@code MapFrameInitializedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param oldFrame The old MapFrame
+     * @param newFrame The new MapFrame
+     */
+    public MapFrameInitializedEvent(Object source, MapFrame oldFrame, MapFrame newFrame) {
+        super(source);
+        this.oldFrame = oldFrame;
+        this.newFrame = newFrame;
+    }
+
+    /**
+     * Returns the old MapFrame.
+     * @return the old MapFrame
+     */
+    public MapFrame getOldMapFrame() {
+        return oldFrame;
+    }
+
+    /**
+     * Returns the new MapFrame.
+     * @return the new MapFrame
+     */
+    public MapFrame getNewMapFrame() {
+        return newFrame;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/MapModeChangeEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/MapModeChangeEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/MapModeChangeEvent.java	(revision 34000)
@@ -0,0 +1,45 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui;
+
+import java.util.EventObject;
+
+import org.openstreetmap.josm.actions.mapmode.MapMode;
+
+/**
+ * Event fired when the map mode changes.
+ */
+public class MapModeChangeEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final MapMode oldMapMode;
+    private final MapMode newMapMode;
+
+    /**
+     * Constructs a new {@code MapModeChangeEvent}.
+     * @param source object on which the Event initially occurred
+     * @param oldMapMode old map mode
+     * @param newMapMode new map mode
+     */
+    public MapModeChangeEvent(Object source, MapMode oldMapMode, MapMode newMapMode) {
+        super(source);
+        this.oldMapMode = oldMapMode;
+        this.newMapMode = newMapMode;
+    }
+
+    /**
+     * Returns the old map mode.
+     * @return the old map mode
+     */
+    public final MapMode getOldMapMode() {
+        return oldMapMode;
+    }
+
+    /**
+     * Returns the new map mode.
+     * @return the new map mode
+     */
+    public final MapMode getNewMapMode() {
+        return newMapMode;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/ZoomChangedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/ZoomChangedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/ZoomChangedEvent.java	(revision 34000)
@@ -0,0 +1,20 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when the zoom changes.
+ */
+public class ZoomChangedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code ZoomChangedEvent}.
+     * @param source object on which the Event initially occurred
+     */
+    public ZoomChangedEvent(Object source) {
+        super(source);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/conflict/tags/NextDecisionEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/conflict/tags/NextDecisionEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/conflict/tags/NextDecisionEvent.java	(revision 34000)
@@ -0,0 +1,20 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.tags;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when the focus goes to next decision.
+ */
+public class NextDecisionEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code NextDecisionEvent}.
+     * @param source object on which the Event initially occurred
+     */
+    public NextDecisionEvent(Object source) {
+        super(source);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/conflict/tags/PreviousDecisionEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/conflict/tags/PreviousDecisionEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/conflict/tags/PreviousDecisionEvent.java	(revision 34000)
@@ -0,0 +1,20 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.conflict.tags;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when the focus goes to previous decision.
+ */
+public class PreviousDecisionEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code PreviousDecisionEvent}.
+     * @param source object on which the Event initially occurred
+     */
+    public PreviousDecisionEvent(Object source) {
+        super(source);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/dialogs/LayerListRefreshedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/dialogs/LayerListRefreshedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/dialogs/LayerListRefreshedEvent.java	(revision 34000)
@@ -0,0 +1,20 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when the list of layers is refreshed.
+ */
+public class LayerListRefreshedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code LayerListRefreshedEvent}.
+     * @param source object on which the Event initially occurred
+     */
+    public LayerListRefreshedEvent(Object source) {
+        super(source);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/dialogs/LayerVisibleEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/dialogs/LayerVisibleEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/dialogs/LayerVisibleEvent.java	(revision 34000)
@@ -0,0 +1,46 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs;
+
+import java.util.EventObject;
+import java.util.Objects;
+
+import org.openstreetmap.josm.gui.layer.Layer;
+
+/**
+ * Event fired when a layer becomes visible.
+ */
+public class LayerVisibleEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final int index;
+    private final Layer layer;
+
+    /**
+     * Constructs a new {@code LayerVisibleEvent}.
+     * @param source object on which the Event initially occurred
+     * @param index layer index
+     * @param layer layer
+     */
+    public LayerVisibleEvent(Object source, int index, Layer layer) {
+        super(source);
+        this.index = index;
+        this.layer = Objects.requireNonNull(layer);
+    }
+
+    /**
+     * Returns the layer index.
+     * @return the layer index
+     */
+    public final int getIndex() {
+        return index;
+    }
+
+    /**
+     * Returns the layer.
+     * @return the layer
+     */
+    public final Layer getLayer() {
+        return layer;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/dialogs/relation/MemberVisibleEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/dialogs/relation/MemberVisibleEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/dialogs/relation/MemberVisibleEvent.java	(revision 34000)
@@ -0,0 +1,32 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.dialogs.relation;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when a relation member becomes visible in the relation dialog.
+ */
+public class MemberVisibleEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final int index;
+
+    /**
+     * Constructs a new {@code MemberVisibleEvent}.
+     * @param source object on which the Event initially occurred
+     * @param index index of the member in the table
+     */
+    public MemberVisibleEvent(Object source, int index) {
+        super(source);
+        this.index = index;
+    }
+
+    /**
+     * Returns member index in member table.
+     * @return member index in member table
+     */
+    public final int getIndex() {
+        return index;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/download/DownloadSourceAddedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/download/DownloadSourceAddedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/download/DownloadSourceAddedEvent.java	(revision 34000)
@@ -0,0 +1,33 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.download;
+
+import java.util.EventObject;
+import java.util.Objects;
+
+/**
+ * Event fired when a download source is added.
+ */
+public class DownloadSourceAddedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final DownloadSource<?> downloadSource;
+
+    /**
+     * Constructs a new {@code DownloadSourceAddedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param downloadSource new download source
+     */
+    public DownloadSourceAddedEvent(Object source, DownloadSource<?> downloadSource) {
+        super(source);
+        this.downloadSource = Objects.requireNonNull(downloadSource);
+    }
+
+    /**
+     * Returns the added download source.
+     * @return the added download source
+     */
+    public DownloadSource<?> getDownloadSource() {
+        return downloadSource;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/layer/LayerStateChangeEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/layer/LayerStateChangeEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/layer/LayerStateChangeEvent.java	(revision 34000)
@@ -0,0 +1,44 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.layer;
+
+import java.util.EventObject;
+import java.util.Objects;
+
+/**
+ * Event fired when the "upload discouraged" (upload=no) state changes.
+ */
+public class LayerStateChangeEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final OsmDataLayer osmDataLayer;
+    private final boolean newValue;
+
+    /**
+     * Constructs a new {@code LayerStateChangeEvent}.
+     * @param source object on which the Event initially occurred
+     * @param layer data layer
+     * @param newValue new value of the "upload discouraged" (upload=no) state
+     */
+    public LayerStateChangeEvent(Object source, OsmDataLayer layer, boolean newValue) {
+        super(source);
+        this.osmDataLayer = Objects.requireNonNull(layer);
+        this.newValue = newValue;
+    }
+
+    /**
+     * Returns the data layer.
+     * @return the data layer
+     */
+    public final OsmDataLayer getDataLayer() {
+        return osmDataLayer;
+    }
+
+    /**
+     * Returns the "upload discouraged" (upload=no) state.
+     * @return the "upload discouraged" (upload=no) state
+     */
+    public final boolean isUploadDiscouraged() {
+        return newValue;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/layer/imagery/FilterChangedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/layer/imagery/FilterChangedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/layer/imagery/FilterChangedEvent.java	(revision 34000)
@@ -0,0 +1,20 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.layer.imagery;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when imagery filters settings change.
+ */
+public class FilterChangedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code FilterChangedEvent}.
+     * @param source object on which the Event initially occurred
+     */
+    public FilterChangedEvent(Object source) {
+        super(source);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/mappaint/MapPaintStyleEntryUpdatedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/mappaint/MapPaintStyleEntryUpdatedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/mappaint/MapPaintStyleEntryUpdatedEvent.java	(revision 34000)
@@ -0,0 +1,32 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when a map paint style entry is updated.
+ */
+public class MapPaintStyleEntryUpdatedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final int index;
+
+    /**
+     * Constructs a new {@code MapPaintStyleEntryUpdatedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param index entry index
+     */
+    public MapPaintStyleEntryUpdatedEvent(Object source, int index) {
+        super(source);
+        this.index = index;
+    }
+
+    /**
+     * Returns entry index.
+     * @return entry index
+     */
+    public final int getIndex() {
+        return index;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/mappaint/MapPaintStylesUpdatedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/mappaint/MapPaintStylesUpdatedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/mappaint/MapPaintStylesUpdatedEvent.java	(revision 34000)
@@ -0,0 +1,20 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.mappaint;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when map paint styles are updated.
+ */
+public class MapPaintStylesUpdatedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code MapPaintStylesUpdatedEvent}.
+     * @param source object on which the Event initially occurred
+     */
+    public MapPaintStylesUpdatedEvent(Object source) {
+        super(source);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/preferences/imagery/ContentValidationEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/preferences/imagery/ContentValidationEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/preferences/imagery/ContentValidationEvent.java	(revision 34000)
@@ -0,0 +1,32 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.preferences.imagery;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when the validation status of the "add imagery" panel changes.
+ */
+public class ContentValidationEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final boolean isValid;
+
+    /**
+     * Constructs a new {@code ContentValidationEvent}.
+     * @param source object on which the Event initially occurred
+     * @param isValid true if the conditions required to close this panel are met
+     */
+    public ContentValidationEvent(Object source, boolean isValid) {
+        super(source);
+        this.isValid = isValid;
+    }
+
+    /**
+     * Determines if the conditions required to close this panel are met.
+     * @return true if the conditions required to close this panel are met
+     */
+    public final boolean isValid() {
+        return isValid;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/preferences/server/ProxyPreferenceChangedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/preferences/server/ProxyPreferenceChangedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/preferences/server/ProxyPreferenceChangedEvent.java	(revision 34000)
@@ -0,0 +1,20 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.preferences.server;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when proxy settings are updated.
+ */
+public class ProxyPreferenceChangedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code ProxyPreferenceChangedEvent}.
+     * @param source object on which the Event initially occurred
+     */
+    public ProxyPreferenceChangedEvent(Object source) {
+        super(source);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/progress/OperationCancelledEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/progress/OperationCancelledEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/progress/OperationCancelledEvent.java	(revision 34000)
@@ -0,0 +1,20 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.progress;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when a task is cancelled.
+ */
+public class OperationCancelledEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code OperationCancelledEvent}.
+     * @param source object on which the Event initially occurred
+     */
+    public OperationCancelledEvent(Object source) {
+        super(source);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetModifiedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetModifiedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/tagging/presets/TaggingPresetModifiedEvent.java	(revision 34000)
@@ -0,0 +1,20 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.tagging.presets;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when a tagging preset is modified.
+ */
+public class TaggingPresetModifiedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructs a new {@code TaggingPresetModifiedEvent}.
+     * @param source object on which the Event initially occurred
+     */
+    public TaggingPresetModifiedEvent(Object source) {
+        super(source);
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/util/KeyPressedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/util/KeyPressedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/util/KeyPressedEvent.java	(revision 34000)
@@ -0,0 +1,33 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.util;
+
+import java.awt.event.KeyEvent;
+import java.util.EventObject;
+
+/**
+ * Event fired when a key is pressed.
+ */
+public class KeyPressedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final KeyEvent keyEvent;
+
+    /**
+     * Constructs a new {@code KeyPressedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param keyEvent the original Swing key event
+     */
+    public KeyPressedEvent(Object source, KeyEvent keyEvent) {
+        super(source);
+        this.keyEvent = keyEvent;
+    }
+
+    /**
+     * Returns the original Swing key event.
+     * @return the original Swing key event
+     */
+    public final KeyEvent getKeyEvent() {
+        return keyEvent;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/util/KeyReleasedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/util/KeyReleasedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/util/KeyReleasedEvent.java	(revision 34000)
@@ -0,0 +1,33 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.util;
+
+import java.awt.event.KeyEvent;
+import java.util.EventObject;
+
+/**
+ * Event fired when a key is released.
+ */
+public class KeyReleasedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final KeyEvent keyEvent;
+
+    /**
+     * Constructs a new {@code KeyReleasedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param keyEvent the original Swing key event
+     */
+    public KeyReleasedEvent(Object source, KeyEvent keyEvent) {
+        super(source);
+        this.keyEvent = keyEvent;
+    }
+
+    /**
+     * Returns the original Swing key event.
+     * @return the original Swing key event
+     */
+    public final KeyEvent getKeyEvent() {
+        return keyEvent;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/util/ModifierExChangedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/util/ModifierExChangedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/gui/util/ModifierExChangedEvent.java	(revision 34000)
@@ -0,0 +1,32 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.gui.util;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when pressed extended modifier keys change is detected.
+ */
+public class ModifierExChangedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final int modifiers;
+
+    /**
+     * Constructs a new {@code ModifierExChangedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param modifiers the new extended modifiers
+     */
+    public ModifierExChangedEvent(Object source, int modifiers) {
+        super(source);
+        this.modifiers = modifiers;
+    }
+
+    /**
+     * Returns the new extended modifiers.
+     * @return the new extended modifiers
+     */
+    public final int getModifiers() {
+        return modifiers;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/io/OsmApiInitializedEvent.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/io/OsmApiInitializedEvent.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/io/OsmApiInitializedEvent.java	(revision 34000)
@@ -0,0 +1,32 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.io;
+
+import java.util.EventObject;
+
+/**
+ * Event fired when the OSM API is initialized.
+ */
+public class OsmApiInitializedEvent extends EventObject {
+
+    private static final long serialVersionUID = 1L;
+
+    private final OsmApi osmApi;
+
+    /**
+     * Constructs a new {@code OsmApiInitializedEvent}.
+     * @param source object on which the Event initially occurred
+     * @param osmApi OSM API
+     */
+    public OsmApiInitializedEvent(Object source, OsmApi osmApi) {
+        super(source);
+        this.osmApi = osmApi;
+    }
+
+    /**
+     * Returns the OSM API.
+     * @return the OSM API
+     */
+    public final OsmApi getOsmApi() {
+        return osmApi;
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/plugins/eventbus/EventBusPlugin.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/plugins/eventbus/EventBusPlugin.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/src/org/openstreetmap/josm/plugins/eventbus/EventBusPlugin.java	(revision 34000)
@@ -0,0 +1,435 @@
+// License: GPL. For details, see LICENSE file.
+package org.openstreetmap.josm.plugins.eventbus;
+
+import static org.openstreetmap.josm.eventbus.JosmEventBus.post;
+
+import java.awt.event.KeyEvent;
+
+import org.openstreetmap.josm.Main;
+import org.openstreetmap.josm.actions.ExpertModeChangedEvent;
+import org.openstreetmap.josm.actions.ExpertToggleAction;
+import org.openstreetmap.josm.actions.ExpertToggleAction.ExpertModeChangeListener;
+import org.openstreetmap.josm.data.CommandQueueEvent;
+import org.openstreetmap.josm.data.SelectionChangedListener;
+import org.openstreetmap.josm.data.SoMChangedEvent;
+import org.openstreetmap.josm.data.SystemOfMeasurement;
+import org.openstreetmap.josm.data.SystemOfMeasurement.SoMChangeListener;
+import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener;
+import org.openstreetmap.josm.data.conflict.ConflictCollection;
+import org.openstreetmap.josm.data.conflict.ConflictsAddedEvent;
+import org.openstreetmap.josm.data.conflict.ConflictsRemovedEvent;
+import org.openstreetmap.josm.data.conflict.IConflictListener;
+import org.openstreetmap.josm.data.gpx.GpxData;
+import org.openstreetmap.josm.data.gpx.GpxData.GpxDataChangeListener;
+import org.openstreetmap.josm.data.gpx.GpxTrack.GpxTrackChangeListener;
+import org.openstreetmap.josm.data.osm.ChangesetCache;
+import org.openstreetmap.josm.data.osm.ChangesetCacheListener;
+import org.openstreetmap.josm.data.osm.DataSelectionListener;
+import org.openstreetmap.josm.data.osm.DataSet;
+import org.openstreetmap.josm.data.osm.HighlightUpdateListener;
+import org.openstreetmap.josm.data.osm.NoteData;
+import org.openstreetmap.josm.data.osm.NoteData.NoteDataUpdateListener;
+import org.openstreetmap.josm.data.osm.NoteDataUpdatedEvent;
+import org.openstreetmap.josm.data.osm.PrimitiveId;
+import org.openstreetmap.josm.data.osm.SelectedNoteChangedEvent;
+import org.openstreetmap.josm.data.osm.event.AbstractDatasetChangedEvent;
+import org.openstreetmap.josm.data.osm.event.DataChangedEvent;
+import org.openstreetmap.josm.data.osm.event.DataSetListener;
+import org.openstreetmap.josm.data.osm.event.NodeMovedEvent;
+import org.openstreetmap.josm.data.osm.event.PrimitivesAddedEvent;
+import org.openstreetmap.josm.data.osm.event.PrimitivesRemovedEvent;
+import org.openstreetmap.josm.data.osm.event.RelationMembersChangedEvent;
+import org.openstreetmap.josm.data.osm.event.TagsChangedEvent;
+import org.openstreetmap.josm.data.osm.event.WayNodesChangedEvent;
+import org.openstreetmap.josm.data.osm.history.HistoryClearedEvent;
+import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
+import org.openstreetmap.josm.data.osm.history.HistoryDataSetListener;
+import org.openstreetmap.josm.data.osm.history.HistoryUpdatedEvent;
+import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
+import org.openstreetmap.josm.data.projection.ProjectionChangedEvent;
+import org.openstreetmap.josm.gui.MainApplication;
+import org.openstreetmap.josm.gui.MapFrame;
+import org.openstreetmap.josm.gui.MapFrame.MapModeChangeListener;
+import org.openstreetmap.josm.gui.MapFrameInitializedEvent;
+import org.openstreetmap.josm.gui.MapModeChangeEvent;
+import org.openstreetmap.josm.gui.NavigatableComponent;
+import org.openstreetmap.josm.gui.NavigatableComponent.ZoomChangeListener;
+import org.openstreetmap.josm.gui.ZoomChangedEvent;
+import org.openstreetmap.josm.gui.conflict.tags.MultiValueCellEditor.NavigationListener;
+import org.openstreetmap.josm.gui.conflict.tags.NextDecisionEvent;
+import org.openstreetmap.josm.gui.conflict.tags.PreviousDecisionEvent;
+import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
+import org.openstreetmap.josm.gui.dialogs.LayerListDialog.LayerListModelListener;
+import org.openstreetmap.josm.gui.dialogs.LayerListRefreshedEvent;
+import org.openstreetmap.josm.gui.dialogs.LayerVisibleEvent;
+import org.openstreetmap.josm.gui.dialogs.relation.IMemberModelListener;
+import org.openstreetmap.josm.gui.dialogs.relation.MemberVisibleEvent;
+import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer;
+import org.openstreetmap.josm.gui.layer.GpxLayer;
+import org.openstreetmap.josm.gui.layer.ImageryLayer;
+import org.openstreetmap.josm.gui.layer.Layer;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
+import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
+import org.openstreetmap.josm.gui.layer.LayerStateChangeEvent;
+import org.openstreetmap.josm.gui.layer.MainLayerManager.ActiveLayerChangeListener;
+import org.openstreetmap.josm.gui.layer.MainLayerManager.LayerAvailabilityEvent;
+import org.openstreetmap.josm.gui.layer.MainLayerManager.LayerAvailabilityListener;
+import org.openstreetmap.josm.gui.layer.MapViewPaintable.PaintableInvalidationListener;
+import org.openstreetmap.josm.gui.layer.NoteLayer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer;
+import org.openstreetmap.josm.gui.layer.OsmDataLayer.LayerStateChangeListener;
+import org.openstreetmap.josm.gui.layer.imagery.FilterChangedEvent;
+import org.openstreetmap.josm.gui.layer.imagery.ImageryFilterSettings.FilterChangeListener;
+import org.openstreetmap.josm.gui.layer.imagery.TileSourceDisplaySettings.DisplaySettingsChangeListener;
+import org.openstreetmap.josm.gui.mappaint.MapPaintStyleEntryUpdatedEvent;
+import org.openstreetmap.josm.gui.mappaint.MapPaintStyles;
+import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.MapPaintSylesUpdateListener;
+import org.openstreetmap.josm.gui.mappaint.MapPaintStylesUpdatedEvent;
+import org.openstreetmap.josm.gui.preferences.imagery.AddImageryPanel.ContentValidationListener;
+import org.openstreetmap.josm.gui.preferences.imagery.ContentValidationEvent;
+import org.openstreetmap.josm.gui.preferences.server.ProxyPreference;
+import org.openstreetmap.josm.gui.preferences.server.ProxyPreferenceChangedEvent;
+import org.openstreetmap.josm.gui.preferences.server.ProxyPreferenceListener;
+import org.openstreetmap.josm.gui.progress.OperationCancelledEvent;
+import org.openstreetmap.josm.gui.progress.ProgressMonitor.CancelListener;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetListener;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetModifiedEvent;
+import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
+import org.openstreetmap.josm.gui.util.KeyPressReleaseListener;
+import org.openstreetmap.josm.gui.util.KeyPressedEvent;
+import org.openstreetmap.josm.gui.util.KeyReleasedEvent;
+import org.openstreetmap.josm.gui.util.ModifierExChangedEvent;
+import org.openstreetmap.josm.gui.util.ModifierExListener;
+import org.openstreetmap.josm.io.OsmApi;
+import org.openstreetmap.josm.io.OsmApi.OsmApiInitializationListener;
+import org.openstreetmap.josm.io.OsmApiInitializedEvent;
+import org.openstreetmap.josm.plugins.Plugin;
+import org.openstreetmap.josm.plugins.PluginInformation;
+import org.openstreetmap.josm.spi.preferences.Config;
+import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener;
+import org.openstreetmap.josm.tools.Logging;
+
+/**
+ * Event bus plugin, providing an event bus more powerful than the traditional listeners registration.
+ */
+public class EventBusPlugin extends Plugin {
+
+    // Defines an instance for each type of listener, and keeps a reference to avoid garbage collection
+
+    private final ActiveLayerChangeListener activeLayerChangeListener = e -> post(e);
+    private final ChangesetCacheListener changesetCacheListener = e -> post(e);
+    private final DataSelectionListener selectionListener = e -> post(e);
+    private final ExpertModeChangeListener expertModeChangeListener = x -> post(new ExpertModeChangedEvent(this, x));
+    private final GpxDataChangeListener gpxChangeListener = e -> post(e);
+    private final GpxTrackChangeListener gpxTrackChangeListener = e -> post(e);
+    private final HighlightUpdateListener highlightUpdateListener = e -> post(e);
+    private final SelectionChangedListener selectionChangedListener = e -> post(e);
+    private final PreferenceChangedListener preferenceChangedListener = e -> post(e);
+    private final PaintableInvalidationListener paintableInvalidationListener = e -> post(e);
+    private final NoteDataUpdateListener noteDataUpdateListener = new NoteDataUpdateListener() {
+        @Override
+        public void selectedNoteChanged(NoteData noteData) {
+            post(new SelectedNoteChangedEvent(this, noteData));
+        }
+
+        @Override
+        public void noteDataUpdated(NoteData data) {
+            post(new NoteDataUpdatedEvent(this, data));
+        }
+    };
+
+    private final IConflictListener conflictsListener = new IConflictListener() {
+        @Override
+        public void onConflictsRemoved(ConflictCollection conflicts) {
+            post(new ConflictsRemovedEvent(this, conflicts));
+        }
+
+        @Override
+        public void onConflictsAdded(ConflictCollection conflicts) {
+            post(new ConflictsAddedEvent(this, conflicts));
+        }
+    };
+
+    private final FilterChangeListener filterChangeListener = () -> post(new FilterChangedEvent(this));
+    private final DisplaySettingsChangeListener displaySettingsChangeListener = e -> post(e);
+
+    private final LayerStateChangeListener layerStateChangeListener =
+            (layer, newValue) -> post(new LayerStateChangeEvent(this, layer, newValue));
+
+    private final LayerAvailabilityListener layerAvailabilityListener = new LayerAvailabilityListener() {
+        @Override
+        public void beforeFirstLayerAdded(LayerAvailabilityEvent e) {
+            post(e);
+        }
+
+        @Override
+        public void afterLastLayerRemoved(LayerAvailabilityEvent e) {
+            post(e);
+        }
+    };
+
+    // CHECKSTYLE.OFF: AnonInnerLengthCheck
+    private final LayerChangeListener layerChangeListener = new LayerChangeListener() {
+        @Override
+        public void layerAdded(LayerAddEvent e) {
+            post(e);
+            Layer layer = e.getAddedLayer();
+            layer.addInvalidationListener(paintableInvalidationListener);
+            if (layer instanceof OsmDataLayer) {
+                ((OsmDataLayer) layer).addLayerStateChangeListener(layerStateChangeListener);
+                DataSet ds = ((OsmDataLayer) layer).data;
+                ds.addDataSetListener(dataSetListener);
+                ds.addHighlightUpdateListener(highlightUpdateListener);
+                ds.addSelectionListener(selectionListener);
+                ds.getConflicts().addConflictListener(conflictsListener);
+            } else if (layer instanceof GpxLayer) {
+                GpxData gpx = ((GpxLayer) layer).data;
+                gpx.addChangeListener(gpxChangeListener);
+                // TODO: cannot add a listener for GpxTrackChangeListener. It would require first new events for tracks being added and removed
+                Logging.debug("TODO: add" + gpxTrackChangeListener);
+            } else if (layer instanceof NoteLayer) {
+                NoteData notes = ((NoteLayer) layer).getNoteData();
+                notes.addNoteDataUpdateListener(noteDataUpdateListener);
+            } else if (layer instanceof ImageryLayer) {
+                ((ImageryLayer) layer).getFilterSettings().addFilterChangeListener(filterChangeListener);
+                if (layer instanceof AbstractTileSourceLayer) {
+                    ((AbstractTileSourceLayer<?>) layer).getDisplaySettings().addSettingsChangeListener(displaySettingsChangeListener);
+                }
+            }
+        }
+
+        @Override
+        public void layerRemoving(LayerRemoveEvent e) {
+            post(e);
+            Layer layer = e.getRemovedLayer();
+            layer.removeInvalidationListener(paintableInvalidationListener);
+            if (layer instanceof OsmDataLayer) {
+                ((OsmDataLayer) layer).removeLayerStateChangeListener(layerStateChangeListener);
+                DataSet ds = ((OsmDataLayer) layer).data;
+                ds.removeDataSetListener(dataSetListener);
+                ds.removeHighlightUpdateListener(highlightUpdateListener);
+                ds.removeSelectionListener(selectionListener);
+                ds.getConflicts().removeConflictListener(conflictsListener);
+                // TODO: cannot add a listener for GpxTrackChangeListener. It would require first new events for tracks being added and removed
+                Logging.debug("TODO: remove" + gpxTrackChangeListener);
+            } else if (layer instanceof GpxLayer) {
+                GpxData gpx = ((GpxLayer) layer).data;
+                gpx.removeChangeListener(gpxChangeListener);
+            } else if (layer instanceof NoteLayer) {
+                NoteData notes = ((NoteLayer) layer).getNoteData();
+                notes.removeNoteDataUpdateListener(noteDataUpdateListener);
+            } else if (layer instanceof ImageryLayer) {
+                ((ImageryLayer) layer).getFilterSettings().removeFilterChangeListener(filterChangeListener);
+                if (layer instanceof AbstractTileSourceLayer) {
+                    ((AbstractTileSourceLayer<?>) layer).getDisplaySettings().removeSettingsChangeListener(displaySettingsChangeListener);
+                }
+            }
+        }
+
+        @Override
+        public void layerOrderChanged(LayerOrderChangeEvent e) {
+            post(e);
+        }
+    };
+    // CHECKSTYLE.ON: AnonInnerLengthCheck
+
+    private final DataSetListener dataSetListener = new DataSetListener() {
+        @Override
+        public void wayNodesChanged(WayNodesChangedEvent event) {
+            post(event);
+        }
+
+        @Override
+        public void tagsChanged(TagsChangedEvent event) {
+            post(event);
+        }
+
+        @Override
+        public void relationMembersChanged(RelationMembersChangedEvent event) {
+            post(event);
+        }
+
+        @Override
+        public void primitivesRemoved(PrimitivesRemovedEvent event) {
+            post(event);
+        }
+
+        @Override
+        public void primitivesAdded(PrimitivesAddedEvent event) {
+            post(event);
+        }
+
+        @Override
+        public void otherDatasetChange(AbstractDatasetChangedEvent event) {
+            post(event);
+        }
+
+        @Override
+        public void nodeMoved(NodeMovedEvent event) {
+            post(event);
+        }
+
+        @Override
+        public void dataChanged(DataChangedEvent event) {
+            post(event);
+        }
+    };
+
+    private final HistoryDataSetListener historyDataSetListener = new HistoryDataSetListener() {
+        @Override
+        public void historyUpdated(HistoryDataSet source, PrimitiveId id) {
+            post(new HistoryUpdatedEvent(this, source, id));
+        }
+
+        @Override
+        public void historyDataSetCleared(HistoryDataSet source) {
+            post(new HistoryClearedEvent(this, source));
+        }
+    };
+
+    private final ProjectionChangeListener projectionChangeListener = (oldValue, newValue) -> {
+        post(new ProjectionChangedEvent(this, oldValue, newValue));
+    };
+
+    private final SoMChangeListener soMChangeListener = (oldSoM, newSoM) -> {
+        post(new SoMChangedEvent(this, oldSoM, newSoM));
+    };
+
+    private final CommandQueueListener commandQueueListener = (queueSize, redoSize) -> {
+        post(new CommandQueueEvent(this, queueSize, redoSize));
+    };
+
+    private final NavigationListener navigationListener = new NavigationListener() {
+        @Override
+        public void gotoNextDecision() {
+            post(new NextDecisionEvent(this));
+        }
+
+        @Override
+        public void gotoPreviousDecision() {
+            post(new PreviousDecisionEvent(this));
+        }
+    };
+
+    private final LayerListModelListener layerListModelListener = new LayerListModelListener() {
+        @Override
+        public void makeVisible(int index, Layer layer) {
+            post(new LayerVisibleEvent(this, index, layer));
+        }
+
+        @Override
+        public void refresh() {
+            post(new LayerListRefreshedEvent(this));
+        }
+    };
+
+    private final IMemberModelListener memberModelListener = index -> post(new MemberVisibleEvent(this, index));
+    // private final DownloadSourceListener downloadSourceListener = source -> post(new DownloadSourceAddedEvent(source)); // TODO: not public
+    private final ContentValidationListener contentValidationListener = isValid -> post(new ContentValidationEvent(this, isValid));
+    private final ProxyPreferenceListener proxyPreferenceListener = () -> post(new ProxyPreferenceChangedEvent(this));
+    private final CancelListener cancelListener = () -> post(new OperationCancelledEvent(this));
+    private final TaggingPresetListener taggingPresetListener = () -> post(new TaggingPresetModifiedEvent(this));
+
+    private final MapPaintSylesUpdateListener mapPaintSylesUpdateListener = new MapPaintSylesUpdateListener() {
+        @Override
+        public void mapPaintStylesUpdated() {
+            post(new MapPaintStylesUpdatedEvent(this));
+        }
+
+        @Override
+        public void mapPaintStyleEntryUpdated(int index) {
+            post(new MapPaintStyleEntryUpdatedEvent(this, index));
+        }
+    };
+
+    private final OsmApiInitializationListener osmApiInitializationListener = api -> post(new OsmApiInitializedEvent(this, api));
+    //private final AudioListener audioListener = url -> post(new AudioPlayingEvent(url)); // TODO: not public
+    private final ZoomChangeListener zoomChangeListener = () -> post(new ZoomChangedEvent(this));
+    private final MapModeChangeListener mapModeChangeListener = (oldMode, newMode) -> post(new MapModeChangeEvent(this, oldMode, newMode));
+    private final ModifierExListener modifierExListener = modifiers -> post(new ModifierExChangedEvent(this, modifiers));
+    private final KeyPressReleaseListener keyPressReleaseListener = new KeyPressReleaseListener() {
+        @Override
+        public void doKeyPressed(KeyEvent e) {
+            post(new KeyPressedEvent(this, e));
+        }
+
+        @Override
+        public void doKeyReleased(KeyEvent e) {
+            post(new KeyReleasedEvent(this, e));
+        }
+    };
+
+    /**
+     * Constructs a new {@code EventBusPlugin}.
+     * @param info plugin information
+     */
+    public EventBusPlugin(PluginInformation info) {
+        super(info);
+        registerAllJosmListeners();
+        // TODO: cannot add a listener for NavigationListener. It would require first new events for MultiValueCellEditor being created
+        Logging.debug("TODO: add" + navigationListener);
+        // TODO: cannot add a listener for IMemberModelListener. It would require first new events for GenericRelationEditor being created
+        Logging.debug("TODO: add" + memberModelListener);
+        // TODO: cannot add a listener for ContentValidationListener. It would require first new events for AddImageryDialog being created
+        Logging.debug("TODO: add" + contentValidationListener);
+        // TODO: cannot add a listener for CancelListener. It would require first new events for ProgressMonitor being created
+        Logging.debug("TODO: add" + cancelListener);
+    }
+
+    void registerAllJosmListeners() {
+        Main.addProjectionChangeListener(projectionChangeListener);
+        MainApplication.getLayerManager().addLayerChangeListener(layerChangeListener);
+        MainApplication.getLayerManager().addActiveLayerChangeListener(activeLayerChangeListener);
+        MainApplication.getLayerManager().addLayerAvailabilityListener(layerAvailabilityListener);
+        MainApplication.undoRedo.addCommandQueueListener(commandQueueListener);
+        ChangesetCache.getInstance().addChangesetCacheListener(changesetCacheListener);
+        DataSet.addSelectionListener(selectionChangedListener);
+        ExpertToggleAction.addExpertModeChangeListener(expertModeChangeListener);
+        HistoryDataSet.getInstance().addHistoryDataSetListener(historyDataSetListener);
+        SystemOfMeasurement.addSoMChangeListener(soMChangeListener);
+        MapPaintStyles.addMapPaintSylesUpdateListener(mapPaintSylesUpdateListener);
+        ProxyPreference.addProxyPreferenceListener(proxyPreferenceListener);
+        MapFrame.addMapModeChangeListener(mapModeChangeListener);
+        NavigatableComponent.addZoomChangeListener(zoomChangeListener);
+        TaggingPresets.addListener(taggingPresetListener);
+        OsmApi.addOsmApiInitializationListener(osmApiInitializationListener);
+        Config.getPref().addPreferenceChangeListener(preferenceChangedListener);
+    }
+
+    void unregisterAllJosmListeners() {
+        Main.removeProjectionChangeListener(projectionChangeListener);
+        MainApplication.getLayerManager().removeLayerChangeListener(layerChangeListener);
+        MainApplication.getLayerManager().removeActiveLayerChangeListener(activeLayerChangeListener);
+        MainApplication.getLayerManager().removeLayerAvailabilityListener(layerAvailabilityListener);
+        MainApplication.undoRedo.removeCommandQueueListener(commandQueueListener);
+        ChangesetCache.getInstance().removeChangesetCacheListener(changesetCacheListener);
+        DataSet.removeSelectionListener(selectionChangedListener);
+        ExpertToggleAction.removeExpertModeChangeListener(expertModeChangeListener);
+        HistoryDataSet.getInstance().removeHistoryDataSetListener(historyDataSetListener);
+        SystemOfMeasurement.removeSoMChangeListener(soMChangeListener);
+        MapPaintStyles.removeMapPaintSylesUpdateListener(mapPaintSylesUpdateListener);
+        ProxyPreference.removeProxyPreferenceListener(proxyPreferenceListener);
+        MapFrame.removeMapModeChangeListener(mapModeChangeListener);
+        NavigatableComponent.removeZoomChangeListener(zoomChangeListener);
+        TaggingPresets.removeListener(taggingPresetListener);
+        OsmApi.removeOsmApiInitializationListener(osmApiInitializationListener);
+        Config.getPref().removePreferenceChangeListener(preferenceChangedListener);
+    }
+
+    @Override
+    public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) {
+        post(new MapFrameInitializedEvent(this, oldFrame, newFrame));
+        if (newFrame != null) {
+            newFrame.getToggleDialog(LayerListDialog.class).getModel().addLayerListModelListener(layerListModelListener);
+            newFrame.keyDetector.addKeyListener(keyPressReleaseListener);
+            newFrame.keyDetector.addModifierExListener(modifierExListener);
+        }
+        if (oldFrame != null) {
+            oldFrame.getToggleDialog(LayerListDialog.class).getModel().removeLayerListModelListener(layerListModelListener);
+            oldFrame.keyDetector.removeKeyListener(keyPressReleaseListener);
+            oldFrame.keyDetector.removeModifierExListener(modifierExListener);
+        }
+    }
+}
Index: /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/AsyncEventBusTest.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/AsyncEventBusTest.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/AsyncEventBusTest.java	(revision 34000)
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2007 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openstreetmap.josm.eventbus.AsyncEventBus;
+
+/**
+ * Test case for {@link AsyncEventBus}.
+ *
+ * @author Cliff Biffle
+ */
+public class AsyncEventBusTest {
+  private static final String EVENT = "Hello";
+
+  /** The executor we use to fake asynchronicity. */
+  private FakeExecutor executor;
+
+  private AsyncEventBus bus;
+
+  @Before
+  public void setUp() throws Exception {
+    executor = new FakeExecutor();
+    bus = new AsyncEventBus(executor);
+  }
+
+  @Test
+  public void testBasicDistribution() {
+    StringCatcher catcher = new StringCatcher();
+    bus.register(catcher);
+
+    // We post the event, but our Executor will not deliver it until instructed.
+    bus.post(EVENT);
+
+    List<String> events = catcher.getEvents();
+    assertTrue("No events should be delivered synchronously.", events.isEmpty());
+
+    // Now we find the task in our Executor and explicitly activate it.
+    List<Runnable> tasks = executor.getTasks();
+    assertEquals("One event dispatch task should be queued.", 1, tasks.size());
+
+    tasks.get(0).run();
+
+    assertEquals("One event should be delivered.", 1, events.size());
+    assertEquals("Correct string should be delivered.", EVENT, events.get(0));
+  }
+
+  /**
+   * An {@link Executor} wanna-be that simply records the tasks it's given. Arguably the Worst
+   * Executor Ever.
+   *
+   * @author cbiffle
+   */
+  public static class FakeExecutor implements Executor {
+    List<Runnable> tasks = new ArrayList<>();
+
+    @Override
+    public void execute(Runnable task) {
+      tasks.add(task);
+    }
+
+    public List<Runnable> getTasks() {
+      return tasks;
+    }
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/DispatcherTest.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/DispatcherTest.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/DispatcherTest.java	(revision 34000)
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2014 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+//import static com.google.common.truth.Truth.assertThat;
+
+//import com.google.common.util.concurrent.Uninterruptibles;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.openstreetmap.josm.eventbus.Dispatcher;
+import org.openstreetmap.josm.eventbus.EventBus;
+import org.openstreetmap.josm.eventbus.Subscribe;
+import org.openstreetmap.josm.eventbus.Subscriber;
+
+/**
+ * Tests for {@link Dispatcher} implementations.
+ *
+ * @author Colin Decker
+ */
+
+public class DispatcherTest {
+
+  private final EventBus bus = new EventBus();
+
+  private final IntegerSubscriber i1 = new IntegerSubscriber("i1");
+  private final IntegerSubscriber i2 = new IntegerSubscriber("i2");
+  private final IntegerSubscriber i3 = new IntegerSubscriber("i3");
+  private final List<Subscriber> integerSubscribers =
+      Arrays.asList(
+          subscriber(bus, i1, "handleInteger", Integer.class),
+          subscriber(bus, i2, "handleInteger", Integer.class),
+          subscriber(bus, i3, "handleInteger", Integer.class));
+
+  private final StringSubscriber s1 = new StringSubscriber("s1");
+  private final StringSubscriber s2 = new StringSubscriber("s2");
+  private final List<Subscriber> stringSubscribers =
+      Arrays.asList(
+          subscriber(bus, s1, "handleString", String.class),
+          subscriber(bus, s2, "handleString", String.class));
+
+  private final ConcurrentLinkedQueue<Object> dispatchedSubscribers =
+      new ConcurrentLinkedQueue<>();
+
+  private Dispatcher dispatcher;
+
+  @Test
+  @Ignore("FIXME")
+  public void testPerThreadQueuedDispatcher() {
+    dispatcher = Dispatcher.perThreadDispatchQueue();
+    dispatcher.dispatch(1, integerSubscribers.iterator());
+/*
+    assertThat(dispatchedSubscribers)
+        .containsExactly(
+            i1,
+            i2,
+            i3, // Integer subscribers are dispatched to first.
+            s1,
+            s2, // Though each integer subscriber dispatches to all string subscribers,
+            s1,
+            s2, // those string subscribers aren't actually dispatched to until all integer
+            s1,
+            s2 // subscribers have finished.
+            )
+        .inOrder();*/
+  }
+
+  @Test
+  @Ignore("FIXME")
+  public void testLegacyAsyncDispatcher() {
+    dispatcher = Dispatcher.legacyAsync();
+
+    final CyclicBarrier barrier = new CyclicBarrier(2);
+    final CountDownLatch latch = new CountDownLatch(2);
+
+    new Thread(
+            new Runnable() {
+              @Override
+              public void run() {
+                try {
+                  barrier.await();
+                } catch (Exception e) {
+                  throw new AssertionError(e);
+                }
+
+                dispatcher.dispatch(2, integerSubscribers.iterator());
+                latch.countDown();
+              }
+            })
+        .start();
+
+    new Thread(
+            new Runnable() {
+              @Override
+              public void run() {
+                try {
+                  barrier.await();
+                } catch (Exception e) {
+                  throw new AssertionError(e);
+                }
+
+                dispatcher.dispatch("foo", stringSubscribers.iterator());
+                latch.countDown();
+              }
+            })
+        .start();
+/*
+    Uninterruptibles.awaitUninterruptibly(latch);
+
+    // See Dispatcher.LegacyAsyncDispatcher for an explanation of why there aren't really any
+    // useful testable guarantees about the behavior of that dispatcher in a multithreaded
+    // environment. Here we simply test that all the expected dispatches happened in some order.
+    assertThat(dispatchedSubscribers).containsExactly(i1, i2, i3, s1, s1, s1, s1, s2, s2, s2, s2);*/
+  }
+
+  @Test
+  @Ignore("FIXME")
+  public void testImmediateDispatcher() {
+    dispatcher = Dispatcher.immediate();
+    dispatcher.dispatch(1, integerSubscribers.iterator());
+/*
+    assertThat(dispatchedSubscribers)
+        .containsExactly(
+            i1, s1, s2, // Each integer subscriber immediately dispatches to 2 string subscribers.
+            i2, s1, s2, i3, s1, s2)
+        .inOrder();*/
+  }
+
+  private static Subscriber subscriber(
+      EventBus bus, Object target, String methodName, Class<?> eventType) {
+    try {
+      return Subscriber.create(bus, target, target.getClass().getMethod(methodName, eventType));
+    } catch (NoSuchMethodException e) {
+      throw new AssertionError(e);
+    }
+  }
+
+  public final class IntegerSubscriber {
+    private final String name;
+
+    public IntegerSubscriber(String name) {
+      this.name = name;
+    }
+
+    @Subscribe
+    public void handleInteger(Integer integer) {
+      dispatchedSubscribers.add(this);
+      dispatcher.dispatch("hello", stringSubscribers.iterator());
+    }
+
+    @Override
+    public String toString() {
+      return name;
+    }
+  }
+
+  public final class StringSubscriber {
+    private final String name;
+
+    public StringSubscriber(String name) {
+      this.name = name;
+    }
+
+    @Subscribe
+    public void handleString(String string) {
+      dispatchedSubscribers.add(this);
+    }
+
+    @Override
+    public String toString() {
+      return name;
+    }
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/EventBusTest.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/EventBusTest.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/EventBusTest.java	(revision 34000)
@@ -0,0 +1,364 @@
+/*
+ * Copyright (C) 2007 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.openstreetmap.josm.eventbus.DeadEvent;
+import org.openstreetmap.josm.eventbus.EventBus;
+import org.openstreetmap.josm.eventbus.Subscribe;
+import org.openstreetmap.josm.eventbus.SubscriberExceptionContext;
+import org.openstreetmap.josm.eventbus.SubscriberExceptionHandler;
+
+/**
+ * Test case for {@link EventBus}.
+ *
+ * @author Cliff Biffle
+ */
+public class EventBusTest {
+  private static final String EVENT = "Hello";
+  private static final String BUS_IDENTIFIER = "test-bus";
+
+  private EventBus bus;
+
+  @Before
+  public void setUp() throws Exception {
+    bus = new EventBus(BUS_IDENTIFIER);
+  }
+
+  @Test
+  public void testBasicCatcherDistribution() {
+    StringCatcher catcher = new StringCatcher();
+    bus.register(catcher);
+    bus.post(EVENT);
+
+    List<String> events = catcher.getEvents();
+    assertEquals("Only one event should be delivered.", 1, events.size());
+    assertEquals("Correct string should be delivered.", EVENT, events.get(0));
+  }
+
+  /**
+   * Tests that events are distributed to any subscribers to their type or any supertype, including
+   * interfaces and superclasses.
+   *
+   * <p>Also checks delivery ordering in such cases.
+   */
+  @Test
+  public void testPolymorphicDistribution() {
+    // Three catchers for related types String, Object, and Comparable<?>.
+    // String isa Object
+    // String isa Comparable<?>
+    // Comparable<?> isa Object
+    StringCatcher stringCatcher = new StringCatcher();
+
+    final List<Object> objectEvents = new ArrayList<>();
+    Object objCatcher =
+        new Object() {
+          @Subscribe
+          public void eat(Object food) {
+            objectEvents.add(food);
+          }
+        };
+
+    final List<Comparable<?>> compEvents = new ArrayList<>();
+    Object compCatcher =
+        new Object() {
+          @Subscribe
+          public void eat(Comparable<?> food) {
+            compEvents.add(food);
+          }
+        };
+    bus.register(stringCatcher);
+    bus.register(objCatcher);
+    bus.register(compCatcher);
+
+    // Two additional event types: Object and Comparable<?> (played by Integer)
+    Object objEvent = new Object();
+    Object compEvent = new Integer(6);
+
+    bus.post(EVENT);
+    bus.post(objEvent);
+    bus.post(compEvent);
+
+    // Check the StringCatcher...
+    List<String> stringEvents = stringCatcher.getEvents();
+    assertEquals("Only one String should be delivered.", 1, stringEvents.size());
+    assertEquals("Correct string should be delivered.", EVENT, stringEvents.get(0));
+
+    // Check the Catcher<Object>...
+    assertEquals("Three Objects should be delivered.", 3, objectEvents.size());
+    assertEquals("String fixture must be first object delivered.", EVENT, objectEvents.get(0));
+    assertEquals("Object fixture must be second object delivered.", objEvent, objectEvents.get(1));
+    assertEquals(
+        "Comparable fixture must be thirdobject delivered.", compEvent, objectEvents.get(2));
+
+    // Check the Catcher<Comparable<?>>...
+    assertEquals("Two Comparable<?>s should be delivered.", 2, compEvents.size());
+    assertEquals("String fixture must be first comparable delivered.", EVENT, compEvents.get(0));
+    assertEquals(
+        "Comparable fixture must be second comparable delivered.", compEvent, compEvents.get(1));
+  }
+
+  @Test
+  public void testSubscriberThrowsException() throws Exception {
+    final RecordingSubscriberExceptionHandler handler = new RecordingSubscriberExceptionHandler();
+    final EventBus eventBus = new EventBus(handler);
+    final RuntimeException exception =
+        new RuntimeException("but culottes have a tendancy to ride up!");
+    final Object subscriber =
+        new Object() {
+          @Subscribe
+          public void throwExceptionOn(String message) {
+            throw exception;
+          }
+        };
+    eventBus.register(subscriber);
+    eventBus.post(EVENT);
+
+    assertEquals("Cause should be available.", exception, handler.exception);
+    assertEquals("EventBus should be available.", eventBus, handler.context.getEventBus());
+    assertEquals("Event should be available.", EVENT, handler.context.getEvent());
+    assertEquals("Subscriber should be available.", subscriber, handler.context.getSubscriber());
+    assertEquals(
+        "Method should be available.",
+        subscriber.getClass().getMethod("throwExceptionOn", String.class),
+        handler.context.getSubscriberMethod());
+  }
+
+  @Test
+  public void testSubscriberThrowsExceptionHandlerThrowsException() throws Exception {
+    final EventBus eventBus =
+        new EventBus(
+            new SubscriberExceptionHandler() {
+              @Override
+              public void handleException(Throwable exception, SubscriberExceptionContext context) {
+                throw new RuntimeException("testSubscriberThrowsExceptionHandlerThrowsException_1. This is a normal exception");
+              }
+            });
+    final Object subscriber =
+        new Object() {
+          @Subscribe
+          public void throwExceptionOn(String message) {
+            throw new RuntimeException("testSubscriberThrowsExceptionHandlerThrowsException_2. This is a normal exception");
+          }
+        };
+    eventBus.register(subscriber);
+    try {
+      eventBus.post(EVENT);
+    } catch (RuntimeException e) {
+      fail("Exception should not be thrown.");
+    }
+  }
+
+  @Test
+  public void testDeadEventForwarding() {
+    GhostCatcher catcher = new GhostCatcher();
+    bus.register(catcher);
+
+    // A String -- an event for which noone has registered.
+    bus.post(EVENT);
+
+    List<DeadEvent> events = catcher.getEvents();
+    assertEquals("One dead event should be delivered.", 1, events.size());
+    assertEquals("The dead event should wrap the original event.", EVENT, events.get(0).getEvent());
+  }
+
+  @Test
+  public void testDeadEventPosting() {
+    GhostCatcher catcher = new GhostCatcher();
+    bus.register(catcher);
+
+    bus.post(new DeadEvent(this, EVENT));
+
+    List<DeadEvent> events = catcher.getEvents();
+    assertEquals("The explicit DeadEvent should be delivered.", 1, events.size());
+    assertEquals("The dead event must not be re-wrapped.", EVENT, events.get(0).getEvent());
+  }
+
+  @Test
+  public void testMissingSubscribe() {
+    bus.register(new Object());
+  }
+
+  @Test
+  public void testUnregister() {
+    StringCatcher catcher1 = new StringCatcher();
+    StringCatcher catcher2 = new StringCatcher();
+    try {
+      bus.unregister(catcher1);
+      fail("Attempting to unregister an unregistered object succeeded");
+    } catch (IllegalArgumentException expected) {
+      // OK.
+    }
+
+    bus.register(catcher1);
+    bus.post(EVENT);
+    bus.register(catcher2);
+    bus.post(EVENT);
+
+    List<String> expectedEvents = new ArrayList<>();
+    expectedEvents.add(EVENT);
+    expectedEvents.add(EVENT);
+
+    assertEquals("Two correct events should be delivered.", expectedEvents, catcher1.getEvents());
+
+    assertEquals(
+        "One correct event should be delivered.", Arrays.asList(EVENT), catcher2.getEvents());
+
+    bus.unregister(catcher1);
+    bus.post(EVENT);
+
+    assertEquals(
+        "Shouldn't catch any more events when unregistered.", expectedEvents, catcher1.getEvents());
+    assertEquals("Two correct events should be delivered.", expectedEvents, catcher2.getEvents());
+
+    try {
+      bus.unregister(catcher1);
+      fail("Attempting to unregister an unregistered object succeeded");
+    } catch (IllegalArgumentException expected) {
+      // OK.
+    }
+
+    bus.unregister(catcher2);
+    bus.post(EVENT);
+    assertEquals(
+        "Shouldn't catch any more events when unregistered.", expectedEvents, catcher1.getEvents());
+    assertEquals(
+        "Shouldn't catch any more events when unregistered.", expectedEvents, catcher2.getEvents());
+  }
+
+  // NOTE: This test will always pass if register() is thread-safe but may also
+  // pass if it isn't, though this is unlikely.
+
+  @Test
+  public void testRegisterThreadSafety() throws Exception {
+    List<StringCatcher> catchers = new CopyOnWriteArrayList<>();
+    List<Future<?>> futures = new ArrayList<>();
+    ExecutorService executor = Executors.newFixedThreadPool(10);
+    int numberOfCatchers = 10000;
+    for (int i = 0; i < numberOfCatchers; i++) {
+      futures.add(executor.submit(new Registrator(bus, catchers)));
+    }
+    for (int i = 0; i < numberOfCatchers; i++) {
+      futures.get(i).get();
+    }
+    assertEquals("Unexpected number of catchers in the list", numberOfCatchers, catchers.size());
+    bus.post(EVENT);
+    List<String> expectedEvents = Arrays.asList(EVENT);
+    for (StringCatcher catcher : catchers) {
+      assertEquals(
+          "One of the registered catchers did not receive an event.",
+          expectedEvents,
+          catcher.getEvents());
+    }
+  }
+
+  @Test
+  public void testToString() throws Exception {
+    EventBus eventBus = new EventBus("a b ; - \" < > / \\ €");
+    assertEquals("EventBus [a b ; - \" < > / \\ €]", eventBus.toString());
+  }
+
+  /**
+   * Tests that bridge methods are not subscribed to events. In Java 8, annotations are included on
+   * the bridge method in addition to the original method, which causes both the original and bridge
+   * methods to be subscribed (since both are annotated @Subscribe) without specifically checking
+   * for bridge methods.
+   */
+  @Test
+  public void testRegistrationWithBridgeMethod() {
+    final AtomicInteger calls = new AtomicInteger();
+    bus.register(
+        new Callback<String>() {
+          @Subscribe
+          @Override
+          public void call(String s) {
+            calls.incrementAndGet();
+          }
+        });
+
+    bus.post("hello");
+
+    assertEquals(1, calls.get());
+  }
+
+  /** Records thrown exception information. */
+  private static final class RecordingSubscriberExceptionHandler
+      implements SubscriberExceptionHandler {
+
+    public SubscriberExceptionContext context;
+    public Throwable exception;
+
+    @Override
+    public void handleException(Throwable exception, SubscriberExceptionContext context) {
+      this.exception = exception;
+      this.context = context;
+    }
+  }
+
+  /** Runnable which registers a StringCatcher on an event bus and adds it to a list. */
+  private static class Registrator implements Runnable {
+    private final EventBus bus;
+    private final List<StringCatcher> catchers;
+
+    Registrator(EventBus bus, List<StringCatcher> catchers) {
+      this.bus = bus;
+      this.catchers = catchers;
+    }
+
+    @Override
+    public void run() {
+      StringCatcher catcher = new StringCatcher();
+      bus.register(catcher);
+      catchers.add(catcher);
+    }
+  }
+
+  /**
+   * A collector for DeadEvents.
+   *
+   * @author cbiffle
+   */
+  public static class GhostCatcher {
+    private List<DeadEvent> events = new ArrayList<>();
+
+    @Subscribe
+    public void ohNoesIHaveDied(DeadEvent event) {
+      events.add(event);
+    }
+
+    public List<DeadEvent> getEvents() {
+      return events;
+    }
+  }
+
+  private interface Callback<T> {
+    void call(T t);
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/ReentrantEventsTest.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/ReentrantEventsTest.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/ReentrantEventsTest.java	(revision 34000)
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2007 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+import org.openstreetmap.josm.eventbus.EventBus;
+import org.openstreetmap.josm.eventbus.Subscribe;
+
+/**
+ * Validate that {@link EventBus} behaves carefully when listeners publish their own events.
+ *
+ * @author Jesse Wilson
+ */
+public class ReentrantEventsTest {
+
+  static final String FIRST = "one";
+  static final Double SECOND = 2.0d;
+
+  final EventBus bus = new EventBus();
+
+  @Test
+  public void testNoReentrantEvents() {
+    ReentrantEventsHater hater = new ReentrantEventsHater();
+    bus.register(hater);
+
+    bus.post(FIRST);
+
+    assertEquals(
+        "ReentrantEventHater expected 2 events",
+        Arrays.asList(FIRST, SECOND),
+        hater.eventsReceived);
+  }
+
+  public class ReentrantEventsHater {
+    boolean ready = true;
+    List<Object> eventsReceived = new ArrayList<>();
+
+    @Subscribe
+    public void listenForStrings(String event) {
+      eventsReceived.add(event);
+      ready = false;
+      try {
+        bus.post(SECOND);
+      } finally {
+        ready = true;
+      }
+    }
+
+    @Subscribe
+    public void listenForDoubles(Double event) {
+      assertTrue("I received an event when I wasn't ready!", ready);
+      eventsReceived.add(event);
+    }
+  }
+
+  @Test
+  public void testEventOrderingIsPredictable() {
+    EventProcessor processor = new EventProcessor();
+    bus.register(processor);
+
+    EventRecorder recorder = new EventRecorder();
+    bus.register(recorder);
+
+    bus.post(FIRST);
+
+    assertEquals(
+        "EventRecorder expected events in order",
+        Arrays.asList(FIRST, SECOND),
+        recorder.eventsReceived);
+  }
+
+  public class EventProcessor {
+    @Subscribe
+    public void listenForStrings(String event) {
+      bus.post(SECOND);
+    }
+  }
+
+  public class EventRecorder {
+    List<Object> eventsReceived = new ArrayList<>();
+
+    @Subscribe
+    public void listenForEverything(Object event) {
+      eventsReceived.add(event);
+    }
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/StringCatcher.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/StringCatcher.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/StringCatcher.java	(revision 34000)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2007 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.openstreetmap.josm.eventbus.Subscribe;
+
+/**
+ * A simple EventSubscriber mock that records Strings.
+ *
+ * <p>For testing fun, also includes a landmine method that EventBus tests are required <em>not</em>
+ * to call ({@link #methodWithoutAnnotation(String)}).
+ *
+ * @author Cliff Biffle
+ */
+public class StringCatcher {
+  private List<String> events = new ArrayList<>();
+
+  @Subscribe
+  public void hereHaveAString(String string) {
+    events.add(string);
+  }
+
+  public void methodWithoutAnnotation(String string) {
+    fail("Event bus must not call methods without @Subscribe!");
+  }
+
+  public List<String> getEvents() {
+    return events;
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/SubscriberRegistryTest.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/SubscriberRegistryTest.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/SubscriberRegistryTest.java	(revision 34000)
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2014 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.openstreetmap.josm.eventbus.EventBus;
+import org.openstreetmap.josm.eventbus.Subscribe;
+import org.openstreetmap.josm.eventbus.Subscriber;
+import org.openstreetmap.josm.eventbus.SubscriberRegistry;
+
+/**
+ * Tests for {@link SubscriberRegistry}.
+ *
+ * @author Colin Decker
+ */
+public class SubscriberRegistryTest {
+
+  private final SubscriberRegistry registry = new SubscriberRegistry(new EventBus());
+
+  @Test
+  public void testRegister() {
+    assertEquals(0, registry.getSubscribersForTesting(String.class).size());
+
+    registry.register(new StringSubscriber());
+    assertEquals(1, registry.getSubscribersForTesting(String.class).size());
+
+    registry.register(new StringSubscriber());
+    assertEquals(2, registry.getSubscribersForTesting(String.class).size());
+
+    registry.register(new ObjectSubscriber());
+    assertEquals(2, registry.getSubscribersForTesting(String.class).size());
+    assertEquals(1, registry.getSubscribersForTesting(Object.class).size());
+  }
+
+  @Test
+  public void testUnregister() {
+    StringSubscriber s1 = new StringSubscriber();
+    StringSubscriber s2 = new StringSubscriber();
+
+    registry.register(s1);
+    registry.register(s2);
+
+    registry.unregister(s1);
+    assertEquals(1, registry.getSubscribersForTesting(String.class).size());
+
+    registry.unregister(s2);
+    assertTrue(registry.getSubscribersForTesting(String.class).isEmpty());
+  }
+
+  @Test
+  public void testUnregister_notRegistered() {
+    try {
+      registry.unregister(new StringSubscriber());
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+
+    StringSubscriber s1 = new StringSubscriber();
+    registry.register(s1);
+    try {
+      registry.unregister(new StringSubscriber());
+      fail();
+    } catch (IllegalArgumentException expected) {
+      // a StringSubscriber was registered, but not the same one we tried to unregister
+    }
+
+    registry.unregister(s1);
+
+    try {
+      registry.unregister(s1);
+      fail();
+    } catch (IllegalArgumentException expected) {
+    }
+  }
+
+  @Test
+  public void testGetSubscribers() {
+    assertEquals(0, size(registry.getSubscribers("")));
+
+    registry.register(new StringSubscriber());
+    assertEquals(1, size(registry.getSubscribers("")));
+
+    registry.register(new StringSubscriber());
+    assertEquals(2, size(registry.getSubscribers("")));
+
+    registry.register(new ObjectSubscriber());
+    assertEquals(3, size(registry.getSubscribers("")));
+    assertEquals(1, size(registry.getSubscribers(new Object())));
+    assertEquals(1, size(registry.getSubscribers(1)));
+
+    registry.register(new IntegerSubscriber());
+    assertEquals(3, size(registry.getSubscribers("")));
+    assertEquals(1, size(registry.getSubscribers(new Object())));
+    assertEquals(2, size(registry.getSubscribers(1)));
+  }
+
+  @Test
+  @Ignore("FIXME")
+  public void testGetSubscribers_returnsImmutableSnapshot() {
+    StringSubscriber s1 = new StringSubscriber();
+    StringSubscriber s2 = new StringSubscriber();
+    ObjectSubscriber o1 = new ObjectSubscriber();
+
+    Iterator<Subscriber> empty = registry.getSubscribers("");
+    assertFalse(empty.hasNext());
+
+    empty = registry.getSubscribers("");
+
+    registry.register(s1);
+    assertFalse(empty.hasNext());
+
+    Iterator<Subscriber> one = registry.getSubscribers("");
+    assertEquals(s1, one.next().target);
+    assertFalse(one.hasNext());
+
+    one = registry.getSubscribers("");
+
+    registry.register(s2);
+    registry.register(o1);
+
+    Iterator<Subscriber> three = registry.getSubscribers("");
+    assertEquals(s1, one.next().target);
+    assertFalse(one.hasNext());
+
+    assertEquals(s1, three.next().target);
+    assertEquals(s2, three.next().target);
+    assertEquals(o1, three.next().target);
+    assertFalse(three.hasNext());
+
+    three = registry.getSubscribers("");
+
+    registry.unregister(s2);
+
+    assertEquals(s1, three.next().target);
+    assertEquals(s2, three.next().target);
+    assertEquals(o1, three.next().target);
+    assertFalse(three.hasNext());
+
+    Iterator<Subscriber> two = registry.getSubscribers("");
+    assertEquals(s1, two.next().target);
+    assertEquals(o1, two.next().target);
+    assertFalse(two.hasNext());
+  }
+
+  public static class StringSubscriber {
+
+    @Subscribe
+    public void handle(String s) {}
+  }
+
+  public static class IntegerSubscriber {
+
+    @Subscribe
+    public void handle(Integer i) {}
+  }
+
+  public static class ObjectSubscriber {
+
+    @Subscribe
+    public void handle(Object o) {}
+  }
+
+  @Test
+  public void testFlattenHierarchy() {
+    assertEquals(
+        new HashSet<>(Arrays.asList(
+            Object.class,
+            HierarchyFixtureInterface.class,
+            HierarchyFixtureSubinterface.class,
+            HierarchyFixtureParent.class,
+            HierarchyFixture.class)),
+        SubscriberRegistry.flattenHierarchy(HierarchyFixture.class));
+  }
+
+  private interface HierarchyFixtureInterface {
+    // Exists only for hierarchy mapping; no members.
+  }
+
+  private interface HierarchyFixtureSubinterface extends HierarchyFixtureInterface {
+    // Exists only for hierarchy mapping; no members.
+  }
+
+  private static class HierarchyFixtureParent implements HierarchyFixtureSubinterface {
+    // Exists only for hierarchy mapping; no members.
+  }
+
+  private static class HierarchyFixture extends HierarchyFixtureParent {
+    // Exists only for hierarchy mapping; no members.
+  }
+  
+  /**
+   * Returns the number of elements remaining in {@code iterator}. The iterator will be left
+   * exhausted: its {@code hasNext()} method will return {@code false}.
+   */
+  public static int size(Iterator<?> iterator) {
+    int count = 0;
+    while (iterator.hasNext()) {
+      iterator.next();
+      count++;
+    }
+    return count;
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/SubscriberTest.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/SubscriberTest.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/SubscriberTest.java	(revision 34000)
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2007 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openstreetmap.josm.eventbus;
+
+//import com.google.common.testing.EqualsTester;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.openstreetmap.josm.eventbus.AllowConcurrentEvents;
+import org.openstreetmap.josm.eventbus.EventBus;
+import org.openstreetmap.josm.eventbus.Subscribe;
+import org.openstreetmap.josm.eventbus.Subscriber;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link Subscriber}.
+ *
+ * @author Cliff Biffle
+ * @author Colin Decker
+ */
+public class SubscriberTest extends TestCase {
+
+  private static final Object FIXTURE_ARGUMENT = new Object();
+
+  private EventBus bus;
+  private boolean methodCalled;
+  private Object methodArgument;
+
+  @Override
+  protected void setUp() throws Exception {
+    bus = new EventBus();
+    methodCalled = false;
+    methodArgument = null;
+  }
+
+  @Test
+  public void testCreate() {
+    Subscriber s1 = Subscriber.create(bus, this, getTestSubscriberMethod("recordingMethod"));
+    assertTrue(s1 instanceof Subscriber.SynchronizedSubscriber);
+
+    // a thread-safe method should not create a synchronized subscriber
+    Subscriber s2 = Subscriber.create(bus, this, getTestSubscriberMethod("threadSafeMethod"));
+    assertFalse(s2 instanceof Subscriber.SynchronizedSubscriber);
+  }
+
+  @Test
+  public void testInvokeSubscriberMethod_basicMethodCall() throws Throwable {
+    Method method = getTestSubscriberMethod("recordingMethod");
+    Subscriber subscriber = Subscriber.create(bus, this, method);
+
+    subscriber.invokeSubscriberMethod(FIXTURE_ARGUMENT);
+
+    assertTrue("Subscriber must call provided method", methodCalled);
+    assertTrue(
+        "Subscriber argument must be exactly the provided object.",
+        methodArgument == FIXTURE_ARGUMENT);
+  }
+
+  @Test
+  public void testInvokeSubscriberMethod_exceptionWrapping() throws Throwable {
+    Method method = getTestSubscriberMethod("exceptionThrowingMethod");
+    Subscriber subscriber = Subscriber.create(bus, this, method);
+
+    try {
+      subscriber.invokeSubscriberMethod(FIXTURE_ARGUMENT);
+      fail("Subscribers whose methods throw must throw InvocationTargetException");
+    } catch (InvocationTargetException expected) {
+      assertTrue(expected.getCause() instanceof IntentionalException);
+    }
+  }
+
+  @Test
+  public void testInvokeSubscriberMethod_errorPassthrough() throws Throwable {
+    Method method = getTestSubscriberMethod("errorThrowingMethod");
+    Subscriber subscriber = Subscriber.create(bus, this, method);
+
+    try {
+      subscriber.invokeSubscriberMethod(FIXTURE_ARGUMENT);
+      fail("Subscribers whose methods throw Errors must rethrow them");
+    } catch (JudgmentError expected) {
+    }
+  }
+
+  @Test
+  @Ignore("FIXME")
+  public void testEquals() throws Exception {
+    /*Method charAt = String.class.getMethod("charAt", int.class);
+    Method concat = String.class.getMethod("concat", String.class);
+    new EqualsTester()
+        .addEqualityGroup(
+            Subscriber.create(bus, "foo", charAt), Subscriber.create(bus, "foo", charAt))
+        .addEqualityGroup(Subscriber.create(bus, "bar", charAt))
+        .addEqualityGroup(Subscriber.create(bus, "foo", concat))
+        .testEquals();*/
+  }
+
+  private Method getTestSubscriberMethod(String name) {
+    try {
+      return getClass().getDeclaredMethod(name, Object.class);
+    } catch (NoSuchMethodException e) {
+      throw new AssertionError();
+    }
+  }
+
+  /**
+   * Records the provided object in {@link #methodArgument} and sets {@link #methodCalled}. This
+   * method is called reflectively by Subscriber during tests, and must remain public.
+   *
+   * @param arg argument to record.
+   */
+  @Subscribe
+  public void recordingMethod(Object arg) {
+    assertFalse(methodCalled);
+    methodCalled = true;
+    methodArgument = arg;
+  }
+
+  @Subscribe
+  public void exceptionThrowingMethod(Object arg) throws Exception {
+    throw new IntentionalException();
+  }
+
+  /** Local exception subclass to check variety of exception thrown. */
+  class IntentionalException extends Exception {
+
+    private static final long serialVersionUID = -2500191180248181379L;
+  }
+
+  @Subscribe
+  public void errorThrowingMethod(Object arg) {
+    throw new JudgmentError();
+  }
+
+  @Subscribe
+  @AllowConcurrentEvents
+  public void threadSafeMethod(Object arg) {}
+
+  /** Local Error subclass to check variety of error thrown. */
+  class JudgmentError extends Error {
+
+    private static final long serialVersionUID = 634248373797713373L;
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/outside/AnnotatedSubscriberFinderTests.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/outside/AnnotatedSubscriberFinderTests.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/outside/AnnotatedSubscriberFinderTests.java	(revision 34000)
@@ -0,0 +1,473 @@
+/*
+ * Copyright (C) 2012 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openstreetmap.josm.eventbus.outside;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.openstreetmap.josm.eventbus.EventBus;
+import org.openstreetmap.josm.eventbus.Subscribe;
+
+/**
+ * Test that EventBus finds the correct subscribers.
+ *
+ * <p>This test must be outside the c.g.c.eventbus package to test correctly.
+ *
+ * @author Louis Wasserman
+ */
+public class AnnotatedSubscriberFinderTests {
+
+  private static final Object EVENT = new Object();
+
+  private abstract static class AbstractEventBusTestParent<H> {
+    abstract H createSubscriber();
+
+    private H subscriber;
+
+    H getSubscriber() {
+      return subscriber;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+      subscriber = createSubscriber();
+      EventBus bus = new EventBus();
+      bus.register(subscriber);
+      bus.post(EVENT);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+      subscriber = null;
+    }
+  }
+
+  /*
+   * We break the tests up based on whether they are annotated or abstract in the superclass.
+   */
+  public static class BaseSubscriberFinderTest
+      extends AbstractEventBusTestParent<BaseSubscriberFinderTest.Subscriber> {
+    static class Subscriber {
+      final List<Object> nonSubscriberEvents = new ArrayList<>();
+      final List<Object> subscriberEvents = new ArrayList<>();
+
+      public void notASubscriber(Object o) {
+        nonSubscriberEvents.add(o);
+      }
+
+      @Subscribe
+      public void subscriber(Object o) {
+        subscriberEvents.add(o);
+      }
+    }
+
+    @Test
+    public void testNonSubscriber() {
+      assertTrue(getSubscriber().nonSubscriberEvents.isEmpty());
+    }
+
+    @Test
+    public void testSubscriber() {
+      assertTrue(getSubscriber().subscriberEvents.contains(EVENT));
+    }
+
+    @Override
+    Subscriber createSubscriber() {
+      return new Subscriber();
+    }
+  }
+
+  public static class AnnotatedAndAbstractInSuperclassTest
+      extends AbstractEventBusTestParent<AnnotatedAndAbstractInSuperclassTest.SubClass> {
+    abstract static class SuperClass {
+      @Subscribe
+      public abstract void overriddenAndAnnotatedInSubclass(Object o);
+
+      @Subscribe
+      public abstract void overriddenInSubclass(Object o);
+    }
+
+    static class SubClass extends SuperClass {
+      final List<Object> overriddenAndAnnotatedInSubclassEvents = new ArrayList<>();
+      final List<Object> overriddenInSubclassEvents = new ArrayList<>();
+
+      @Subscribe
+      @Override
+      public void overriddenAndAnnotatedInSubclass(Object o) {
+        overriddenAndAnnotatedInSubclassEvents.add(o);
+      }
+
+      @Override
+      public void overriddenInSubclass(Object o) {
+        overriddenInSubclassEvents.add(o);
+      }
+    }
+
+    @Test
+    public void testOverriddenAndAnnotatedInSubclass() {
+      assertTrue(getSubscriber().overriddenAndAnnotatedInSubclassEvents.contains(EVENT));
+    }
+
+    @Test
+    public void testOverriddenNotAnnotatedInSubclass() {
+      assertTrue(getSubscriber().overriddenInSubclassEvents.contains(EVENT));
+    }
+
+    @Override
+    SubClass createSubscriber() {
+      return new SubClass();
+    }
+  }
+
+  public static class AnnotatedNotAbstractInSuperclassTest
+      extends AbstractEventBusTestParent<AnnotatedNotAbstractInSuperclassTest.SubClass> {
+    static class SuperClass {
+      final List<Object> notOverriddenInSubclassEvents = new ArrayList<>();
+      final List<Object> overriddenNotAnnotatedInSubclassEvents = new ArrayList<>();
+      final List<Object> overriddenAndAnnotatedInSubclassEvents = new ArrayList<>();
+      final List<Object> differentlyOverriddenNotAnnotatedInSubclassBadEvents =
+          new ArrayList<>();
+      final List<Object> differentlyOverriddenAnnotatedInSubclassBadEvents = new ArrayList<>();
+
+      @Subscribe
+      public void notOverriddenInSubclass(Object o) {
+        notOverriddenInSubclassEvents.add(o);
+      }
+
+      @Subscribe
+      public void overriddenNotAnnotatedInSubclass(Object o) {
+        overriddenNotAnnotatedInSubclassEvents.add(o);
+      }
+
+      @Subscribe
+      public void overriddenAndAnnotatedInSubclass(Object o) {
+        overriddenAndAnnotatedInSubclassEvents.add(o);
+      }
+
+      @Subscribe
+      public void differentlyOverriddenNotAnnotatedInSubclass(Object o) {
+        // the subclass overrides this and does *not* call super.dONAIS(o)
+        differentlyOverriddenNotAnnotatedInSubclassBadEvents.add(o);
+      }
+
+      @Subscribe
+      public void differentlyOverriddenAnnotatedInSubclass(Object o) {
+        // the subclass overrides this and does *not* call super.dOAIS(o)
+        differentlyOverriddenAnnotatedInSubclassBadEvents.add(o);
+      }
+    }
+
+    static class SubClass extends SuperClass {
+      final List<Object> differentlyOverriddenNotAnnotatedInSubclassGoodEvents =
+          new ArrayList<>();
+      final List<Object> differentlyOverriddenAnnotatedInSubclassGoodEvents = new ArrayList<>();
+
+      @Override
+      public void overriddenNotAnnotatedInSubclass(Object o) {
+        super.overriddenNotAnnotatedInSubclass(o);
+      }
+
+      @Subscribe
+      @Override
+      public void overriddenAndAnnotatedInSubclass(Object o) {
+        super.overriddenAndAnnotatedInSubclass(o);
+      }
+
+      @Override
+      public void differentlyOverriddenNotAnnotatedInSubclass(Object o) {
+        differentlyOverriddenNotAnnotatedInSubclassGoodEvents.add(o);
+      }
+
+      @Subscribe
+      @Override
+      public void differentlyOverriddenAnnotatedInSubclass(Object o) {
+        differentlyOverriddenAnnotatedInSubclassGoodEvents.add(o);
+      }
+    }
+
+    @Test
+    public void testNotOverriddenInSubclass() {
+      assertTrue(getSubscriber().notOverriddenInSubclassEvents.contains(EVENT));
+    }
+
+    @Test
+    public void testOverriddenNotAnnotatedInSubclass() {
+      assertTrue(getSubscriber().overriddenNotAnnotatedInSubclassEvents.contains(EVENT));
+    }
+
+    @Test
+    public void testDifferentlyOverriddenNotAnnotatedInSubclass() {
+      assertTrue(getSubscriber().differentlyOverriddenNotAnnotatedInSubclassGoodEvents
+          .contains(EVENT));
+      assertTrue(getSubscriber().differentlyOverriddenNotAnnotatedInSubclassBadEvents.isEmpty());
+    }
+
+    @Test
+    public void testOverriddenAndAnnotatedInSubclass() {
+      assertTrue(getSubscriber().overriddenAndAnnotatedInSubclassEvents.contains(EVENT));
+    }
+
+    @Test
+    public void testDifferentlyOverriddenAndAnnotatedInSubclass() {
+      assertTrue(getSubscriber().differentlyOverriddenAnnotatedInSubclassGoodEvents
+          .contains(EVENT));
+      assertTrue(getSubscriber().differentlyOverriddenAnnotatedInSubclassBadEvents.isEmpty());
+    }
+
+    @Override
+    SubClass createSubscriber() {
+      return new SubClass();
+    }
+  }
+
+  public static class AbstractNotAnnotatedInSuperclassTest
+      extends AbstractEventBusTestParent<AbstractNotAnnotatedInSuperclassTest.SubClass> {
+    abstract static class SuperClass {
+      public abstract void overriddenInSubclassNowhereAnnotated(Object o);
+
+      public abstract void overriddenAndAnnotatedInSubclass(Object o);
+    }
+
+    static class SubClass extends SuperClass {
+      final List<Object> overriddenInSubclassNowhereAnnotatedEvents = new ArrayList<>();
+      final List<Object> overriddenAndAnnotatedInSubclassEvents = new ArrayList<>();
+
+      @Override
+      public void overriddenInSubclassNowhereAnnotated(Object o) {
+        overriddenInSubclassNowhereAnnotatedEvents.add(o);
+      }
+
+      @Subscribe
+      @Override
+      public void overriddenAndAnnotatedInSubclass(Object o) {
+        overriddenAndAnnotatedInSubclassEvents.add(o);
+      }
+    }
+
+    @Test
+    public void testOverriddenAndAnnotatedInSubclass() {
+      assertTrue(getSubscriber().overriddenAndAnnotatedInSubclassEvents.contains(EVENT));
+    }
+
+    @Test
+    public void testOverriddenInSubclassNowhereAnnotated() {
+      assertTrue(getSubscriber().overriddenInSubclassNowhereAnnotatedEvents.isEmpty());
+    }
+
+    @Override
+    SubClass createSubscriber() {
+      return new SubClass();
+    }
+  }
+
+  public static class NeitherAbstractNorAnnotatedInSuperclassTest
+      extends AbstractEventBusTestParent<NeitherAbstractNorAnnotatedInSuperclassTest.SubClass> {
+    static class SuperClass {
+      final List<Object> neitherOverriddenNorAnnotatedEvents = new ArrayList<>();
+      final List<Object> overriddenInSubclassNowhereAnnotatedEvents = new ArrayList<>();
+      final List<Object> overriddenAndAnnotatedInSubclassEvents = new ArrayList<>();
+
+      public void neitherOverriddenNorAnnotated(Object o) {
+        neitherOverriddenNorAnnotatedEvents.add(o);
+      }
+
+      public void overriddenInSubclassNowhereAnnotated(Object o) {
+        overriddenInSubclassNowhereAnnotatedEvents.add(o);
+      }
+
+      public void overriddenAndAnnotatedInSubclass(Object o) {
+        overriddenAndAnnotatedInSubclassEvents.add(o);
+      }
+    }
+
+    static class SubClass extends SuperClass {
+      @Override
+      public void overriddenInSubclassNowhereAnnotated(Object o) {
+        super.overriddenInSubclassNowhereAnnotated(o);
+      }
+
+      @Subscribe
+      @Override
+      public void overriddenAndAnnotatedInSubclass(Object o) {
+        super.overriddenAndAnnotatedInSubclass(o);
+      }
+    }
+
+    @Test
+    public void testNeitherOverriddenNorAnnotated() {
+      assertTrue(getSubscriber().neitherOverriddenNorAnnotatedEvents.isEmpty());
+    }
+
+    @Test
+    public void testOverriddenInSubclassNowhereAnnotated() {
+      assertTrue(getSubscriber().overriddenInSubclassNowhereAnnotatedEvents.isEmpty());
+    }
+
+    @Test
+    public void testOverriddenAndAnnotatedInSubclass() {
+      assertTrue(getSubscriber().overriddenAndAnnotatedInSubclassEvents.contains(EVENT));
+    }
+
+    @Override
+    SubClass createSubscriber() {
+      return new SubClass();
+    }
+  }
+
+  public static class DeepInterfaceTest
+      extends AbstractEventBusTestParent<DeepInterfaceTest.SubscriberClass> {
+    interface Interface1 {
+      @Subscribe
+      void annotatedIn1(Object o);
+
+      @Subscribe
+      void annotatedIn1And2(Object o);
+
+      @Subscribe
+      void annotatedIn1And2AndClass(Object o);
+
+      void declaredIn1AnnotatedIn2(Object o);
+
+      void declaredIn1AnnotatedInClass(Object o);
+
+      void nowhereAnnotated(Object o);
+    }
+
+    interface Interface2 extends Interface1 {
+      @Override
+      @Subscribe
+      void declaredIn1AnnotatedIn2(Object o);
+
+      @Override
+      @Subscribe
+      void annotatedIn1And2(Object o);
+
+      @Override
+      @Subscribe
+      void annotatedIn1And2AndClass(Object o);
+
+      void declaredIn2AnnotatedInClass(Object o);
+
+      @Subscribe
+      void annotatedIn2(Object o);
+    }
+
+    static class SubscriberClass implements Interface2 {
+      final List<Object> annotatedIn1Events = new ArrayList<>();
+      final List<Object> annotatedIn1And2Events = new ArrayList<>();
+      final List<Object> annotatedIn1And2AndClassEvents = new ArrayList<>();
+      final List<Object> declaredIn1AnnotatedIn2Events = new ArrayList<>();
+      final List<Object> declaredIn1AnnotatedInClassEvents = new ArrayList<>();
+      final List<Object> declaredIn2AnnotatedInClassEvents = new ArrayList<>();
+      final List<Object> annotatedIn2Events = new ArrayList<>();
+      final List<Object> nowhereAnnotatedEvents = new ArrayList<>();
+
+      @Override
+      public void annotatedIn1(Object o) {
+        annotatedIn1Events.add(o);
+      }
+
+      @Subscribe
+      @Override
+      public void declaredIn1AnnotatedInClass(Object o) {
+        declaredIn1AnnotatedInClassEvents.add(o);
+      }
+
+      @Override
+      public void declaredIn1AnnotatedIn2(Object o) {
+        declaredIn1AnnotatedIn2Events.add(o);
+      }
+
+      @Override
+      public void annotatedIn1And2(Object o) {
+        annotatedIn1And2Events.add(o);
+      }
+
+      @Subscribe
+      @Override
+      public void annotatedIn1And2AndClass(Object o) {
+        annotatedIn1And2AndClassEvents.add(o);
+      }
+
+      @Subscribe
+      @Override
+      public void declaredIn2AnnotatedInClass(Object o) {
+        declaredIn2AnnotatedInClassEvents.add(o);
+      }
+
+      @Override
+      public void annotatedIn2(Object o) {
+        annotatedIn2Events.add(o);
+      }
+
+      @Override
+      public void nowhereAnnotated(Object o) {
+        nowhereAnnotatedEvents.add(o);
+      }
+    }
+
+    @Test
+    public void testAnnotatedIn1() {
+      assertTrue(getSubscriber().annotatedIn1Events.contains(EVENT));
+    }
+
+    @Test
+    public void testAnnotatedIn2() {
+      assertTrue(getSubscriber().annotatedIn2Events.contains(EVENT));
+    }
+
+    @Test
+    public void testAnnotatedIn1And2() {
+      assertTrue(getSubscriber().annotatedIn1And2Events.contains(EVENT));
+    }
+
+    @Test
+    public void testAnnotatedIn1And2AndClass() {
+      assertTrue(getSubscriber().annotatedIn1And2AndClassEvents.contains(EVENT));
+    }
+
+    @Test
+    public void testDeclaredIn1AnnotatedIn2() {
+      assertTrue(getSubscriber().declaredIn1AnnotatedIn2Events.contains(EVENT));
+    }
+
+    @Test
+    public void testDeclaredIn1AnnotatedInClass() {
+      assertTrue(getSubscriber().declaredIn1AnnotatedInClassEvents.contains(EVENT));
+    }
+
+    @Test
+    public void testDeclaredIn2AnnotatedInClass() {
+      assertTrue(getSubscriber().declaredIn2AnnotatedInClassEvents.contains(EVENT));
+    }
+
+    @Test
+    public void testNowhereAnnotated() {
+      assertTrue(getSubscriber().nowhereAnnotatedEvents.isEmpty());
+    }
+
+    @Override
+    SubscriberClass createSubscriber() {
+      return new SubscriberClass();
+    }
+  }
+}
Index: /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/outside/OutsideEventBusTest.java
===================================================================
--- /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/outside/OutsideEventBusTest.java	(revision 34000)
+++ /applications/editors/josm/plugins/eventbus/test/unit/org/openstreetmap/josm/eventbus/outside/OutsideEventBusTest.java	(revision 34000)
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2011 The Guava Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.openstreetmap.josm.eventbus.outside;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.Test;
+import org.openstreetmap.josm.eventbus.EventBus;
+import org.openstreetmap.josm.eventbus.Subscribe;
+
+/**
+ * Test cases for {@code EventBus} that must not be in the same package.
+ *
+ * @author Louis Wasserman
+ */
+public class OutsideEventBusTest {
+
+  /*
+   * If you do this test from common.eventbus.EventBusTest, it doesn't actually test the behavior.
+   * That is, even if exactly the same method works from inside the common.eventbus package tests,
+   * it can fail here.
+   */
+  @Test
+  public void testAnonymous() {
+    final AtomicReference<String> holder = new AtomicReference<>();
+    final AtomicInteger deliveries = new AtomicInteger();
+    EventBus bus = new EventBus();
+    bus.register(
+        new Object() {
+          @Subscribe
+          public void accept(String str) {
+            holder.set(str);
+            deliveries.incrementAndGet();
+          }
+        });
+
+    String EVENT = "Hello!";
+    bus.post(EVENT);
+
+    assertEquals("Only one event should be delivered.", 1, deliveries.get());
+    assertEquals("Correct string should be delivered.", EVENT, holder.get());
+  }
+}
