diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java index 4a101cf2..1e2e4abe 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java +++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactory.java @@ -34,6 +34,15 @@ public interface MarkwonSpansFactory { @NonNull Builder setFactory(@NonNull Class node, @Nullable SpanFactory factory); + /** + * Helper method to add a {@link SpanFactory} for a Node. This method will merge existing + * {@link SpanFactory} with the specified one. + * + * @since 3.0.1-SNAPSHOT + */ + @NonNull + Builder addFactory(@NonNull Class node, @NonNull SpanFactory factory); + /** * Can be useful when enhancing an already defined SpanFactory with another one. */ diff --git a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java index 3acce5c1..85a4ab33 100644 --- a/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java +++ b/markwon-core/src/main/java/ru/noties/markwon/MarkwonSpansFactoryImpl.java @@ -5,8 +5,10 @@ import android.support.annotation.Nullable; import org.commonmark.node.Node; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -52,6 +54,27 @@ class MarkwonSpansFactoryImpl implements MarkwonSpansFactory { return this; } + @NonNull + @Override + public Builder addFactory(@NonNull Class node, @NonNull SpanFactory factory) { + // if there is no factory registered for this node -> just add it + final SpanFactory existing = factories.get(node); + if (existing == null) { + factories.put(node, factory); + } else { + // existing span factory can be of CompositeSpanFactory at this point -> append to it + if (existing instanceof CompositeSpanFactory) { + ((CompositeSpanFactory) existing).factories.add(factory); + } else { + // if it's not composite at this point -> make it + final CompositeSpanFactory compositeSpanFactory = + new CompositeSpanFactory(existing, factory); + factories.put(node, compositeSpanFactory); + } + } + return this; + } + @Nullable @Override public SpanFactory getFactory(@NonNull Class node) { @@ -74,4 +97,28 @@ class MarkwonSpansFactoryImpl implements MarkwonSpansFactory { return new MarkwonSpansFactoryImpl(Collections.unmodifiableMap(factories)); } } + + static class CompositeSpanFactory implements SpanFactory { + + final List factories; + + CompositeSpanFactory(@NonNull SpanFactory first, @NonNull SpanFactory second) { + this.factories = new ArrayList<>(3); + this.factories.add(first); + this.factories.add(second); + } + + @Nullable + @Override + public Object getSpans(@NonNull MarkwonConfiguration configuration, @NonNull RenderProps props) { + // please note that we do not check it factory itself returns an array of spans, + // as this behaviour is supported now (previously we supported only a single-level array) + final int length = factories.size(); + final Object[] out = new Object[length]; + for (int i = 0; i < length; i++) { + out[i] = factories.get(i).getSpans(configuration, props); + } + return out; + } + } } diff --git a/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java b/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java index 6245df0e..b66cfd0c 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/MarkwonSpansFactoryImplTest.java @@ -13,13 +13,18 @@ import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.Arrays; import java.util.Collections; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; @RunWith(RobolectricTestRunner.class) @Config(manifest = Config.NONE) @@ -87,4 +92,58 @@ public class MarkwonSpansFactoryImplTest { assertNull(factory.get(type)); } } + + @Test + public void composite_span_factory() { + // validate that composite span factory returns (calls) all span-factories + + final SpanFactory first = mock(SpanFactory.class); + final SpanFactory second = mock(SpanFactory.class); + + final MarkwonSpansFactoryImpl.CompositeSpanFactory factory = + new MarkwonSpansFactoryImpl.CompositeSpanFactory(first, second); + + final Object spans = factory.getSpans(mock(MarkwonConfiguration.class), mock(RenderProps.class)); + assertNotNull(spans); + assertTrue(spans.getClass().isArray()); + assertEquals(2, ((Object[]) spans).length); + + verify(first, times(1)).getSpans(any(MarkwonConfiguration.class), any(RenderProps.class)); + verify(second, times(1)).getSpans(any(MarkwonConfiguration.class), any(RenderProps.class)); + } + + @Test + public void builder_add_factory() { + // here is what we should validate: + // * if we call addFactory and there is none already -> supplied factory + // * if there is + // * * if not composite -> make composite + // * * if composite -> add to it + + final MarkwonSpansFactoryImpl.BuilderImpl builder = new MarkwonSpansFactoryImpl.BuilderImpl(); + + final SpanFactory first = mock(SpanFactory.class); + final SpanFactory second = mock(SpanFactory.class); + final SpanFactory third = mock(SpanFactory.class); + + final Class node = Node.class; + + // assert none yet + assertNull(builder.getFactory(node)); + + // add first, none yet -> it should be added without modifications + builder.addFactory(node, first); + assertEquals(first, builder.getFactory(node)); + + // add second -> composite factory will be created + builder.addFactory(node, second); + final MarkwonSpansFactoryImpl.CompositeSpanFactory compositeSpanFactory = + (MarkwonSpansFactoryImpl.CompositeSpanFactory) builder.getFactory(node); + assertNotNull(compositeSpanFactory); + assertEquals(Arrays.asList(first, second), compositeSpanFactory.factories); + + builder.addFactory(node, third); + assertEquals(compositeSpanFactory, builder.getFactory(node)); + assertEquals(Arrays.asList(first, second, third), compositeSpanFactory.factories); + } } \ No newline at end of file diff --git a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java b/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java index f3edf945..dd1d43cc 100644 --- a/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java +++ b/markwon-core/src/test/java/ru/noties/markwon/core/CorePluginTest.java @@ -156,6 +156,12 @@ public class CorePluginTest { return this; } + @NonNull + @Override + public MarkwonSpansFactory.Builder addFactory(@NonNull Class node, @NonNull SpanFactory factory) { + throw new RuntimeException(); + } + @Nullable @Override public SpanFactory getFactory(@NonNull Class node) {