SharedTagContent { public void addContentChangeListener(final AbstractHtml tag, final ContentChangeListener<T> contentChangeListener) { final long stamp = lock.writeLock(); try { final Set<ContentChangeListener<T>> listeners; if (contentChangeListeners == null) { listeners = new LinkedHashSet<>(4); contentChangeListeners = new WeakHashMap<>(4, 0.75F); contentChangeListeners.put(tag, listeners); } else { listeners = contentChangeListeners.computeIfAbsent(tag, k -> new LinkedHashSet<>(4)); } listeners.add(contentChangeListener); } finally { lock.unlockWrite(stamp); } } SharedTagContent(final T content);  SharedTagContent(final UpdateClientNature updateClientNature,
            final T content);  SharedTagContent(final T content, final boolean contentTypeHtml);  SharedTagContent(final UpdateClientNature updateClientNature,
            final T content, final boolean contentTypeHtml);  SharedTagContent(final boolean shared, final T content,
            final boolean contentTypeHtml);  SharedTagContent(final boolean shared, final T content); T getContent(); boolean isContentTypeHtml(); boolean isShared(); UpdateClientNature getUpdateClientNature(); void setUpdateClientNature(
            final UpdateClientNature updateClientNature); boolean isUpdateClient(); void setUpdateClient(final boolean updateClient); void setShared(final boolean shared); void setContent(final T content); void setContent(final UpdateClientNature updateClientNature,
            final T content); void setContent(final T content, final boolean contentTypeHtml); void setContent(final UpdateClientNature updateClientNature,
            final T content, final boolean contentTypeHtml); void setContent(final Set<AbstractHtml> exclusionTags,
            final T content); void setContent(final Set<AbstractHtml> exclusionTags,
            final T content, final boolean contentTypeHtml); void detach(final Set<AbstractHtml> exclusionTags); void detach(final boolean removeContent); void detach(final boolean removeContent,
            final Set<AbstractHtml> exclusionTags); void detach(final boolean removeContent,
            final Set<AbstractHtml> exclusionTags,
            final Set<AbstractHtml> exclusionClientUpdateTags); void addContentChangeListener(final AbstractHtml tag,
            final ContentChangeListener<T> contentChangeListener); void addDetachListener(final AbstractHtml tag,
            final DetachListener<T> detachListener); void removeContentChangeListener(
            final ContentChangeListener<T> contentChangeListener); void removeContentChangeListener(final AbstractHtml tag,
            final ContentChangeListener<T> contentChangeListener); void removeDetachListener(final DetachListener<T> detachListener); void removeDetachListener(final AbstractHtml tag,
            final DetachListener<T> detachListener); void removeContentChangeListeners(final AbstractHtml tag,
            final Collection<ContentChangeListener<T>> contentChangeListeners); void removeDetachListeners(final AbstractHtml tag,
            final Collection<DetachListener<T>> detachListeners); void removeAllContentChangeListeners(final AbstractHtml tag); void removeAllContentChangeListeners(); void removeAllDetachListeners(final AbstractHtml tag); void removeAllDetachListeners(); ContentFormatter<T> getContentFormatter(final AbstractHtml tag); Set<ContentChangeListener<T>> getContentChangeListeners(
            final AbstractHtml tag); Set<DetachListener<T>> getDetachListeners(final AbstractHtml tag);  }
@Test public void testAddContentChangeListener() { { SharedTagContent<String> stc = new SharedTagContent<>(UpdateClientNature.ALLOW_PARALLEL, "Test content", true); Div div = new Div(null); Span span1 = new Span(div); span1.addInnerHtml(stc); Span span2 = new Span(div); span2.addInnerHtml(stc); AtomicBoolean listenerInvoked = new AtomicBoolean(); stc.addContentChangeListener(span2, new SharedTagContent.ContentChangeListener<String>() { @Override public Runnable contentChanged(ChangeEvent<String> changeEvent) { listenerInvoked.set(true); assertEquals("Test content", changeEvent.getContentBefore().getContent()); assertEquals("Content Changed", changeEvent.getContentAfter().getContent()); CompletableFuture.runAsync(() -> {assertEquals("Content Changed", stc.getContent());}); return () -> { assertEquals("Content Changed", stc.getContent());}; } }); assertEquals("Test content", stc.getContent()); assertEquals("<div><span>Test content</span><span>Test content</span></div>", div.toHtmlString()); stc.setContent("Content Changed"); assertEquals("Content Changed", stc.getContent()); assertEquals("<div><span>Content Changed</span><span>Content Changed</span></div>", div.toHtmlString()); assertTrue(stc.isContentTypeHtml()); assertEquals(UpdateClientNature.ALLOW_PARALLEL, stc.getUpdateClientNature()); assertTrue(listenerInvoked.get()); } }