Introduce MarkwonVisitorFactory
This commit is contained in:
parent
3c77448682
commit
4406a5faaf
@ -104,11 +104,17 @@ class MarkwonBuilderImpl implements Markwon.Builder {
|
|||||||
|
|
||||||
final RenderProps renderProps = new RenderPropsImpl();
|
final RenderProps renderProps = new RenderPropsImpl();
|
||||||
|
|
||||||
|
// @since 4.1.1-SNAPSHOT
|
||||||
|
final MarkwonVisitorFactory visitorFactory = MarkwonVisitorFactory.create(
|
||||||
|
visitorBuilder,
|
||||||
|
configuration,
|
||||||
|
renderProps);
|
||||||
|
|
||||||
return new MarkwonImpl(
|
return new MarkwonImpl(
|
||||||
bufferType,
|
bufferType,
|
||||||
textSetter,
|
textSetter,
|
||||||
parserBuilder.build(),
|
parserBuilder.build(),
|
||||||
visitorBuilder.build(configuration, renderProps),
|
visitorFactory,
|
||||||
Collections.unmodifiableList(plugins)
|
Collections.unmodifiableList(plugins)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class MarkwonImpl extends Markwon {
|
|||||||
|
|
||||||
private final TextView.BufferType bufferType;
|
private final TextView.BufferType bufferType;
|
||||||
private final Parser parser;
|
private final Parser parser;
|
||||||
private final MarkwonVisitor visitor;
|
private final MarkwonVisitorFactory visitorFactory; // @since 4.1.1-SNAPSHOT
|
||||||
private final List<MarkwonPlugin> plugins;
|
private final List<MarkwonPlugin> plugins;
|
||||||
|
|
||||||
// @since 4.1.0
|
// @since 4.1.0
|
||||||
@ -31,12 +31,12 @@ class MarkwonImpl extends Markwon {
|
|||||||
@NonNull TextView.BufferType bufferType,
|
@NonNull TextView.BufferType bufferType,
|
||||||
@Nullable TextSetter textSetter,
|
@Nullable TextSetter textSetter,
|
||||||
@NonNull Parser parser,
|
@NonNull Parser parser,
|
||||||
@NonNull MarkwonVisitor visitor,
|
@NonNull MarkwonVisitorFactory visitorFactory,
|
||||||
@NonNull List<MarkwonPlugin> plugins) {
|
@NonNull List<MarkwonPlugin> plugins) {
|
||||||
this.bufferType = bufferType;
|
this.bufferType = bufferType;
|
||||||
this.textSetter = textSetter;
|
this.textSetter = textSetter;
|
||||||
this.parser = parser;
|
this.parser = parser;
|
||||||
this.visitor = visitor;
|
this.visitorFactory = visitorFactory;
|
||||||
this.plugins = plugins;
|
this.plugins = plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,16 +60,22 @@ class MarkwonImpl extends Markwon {
|
|||||||
plugin.beforeRender(node);
|
plugin.beforeRender(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @since 4.1.1-SNAPSHOT obtain visitor via factory
|
||||||
|
final MarkwonVisitor visitor = visitorFactory.create();
|
||||||
|
|
||||||
node.accept(visitor);
|
node.accept(visitor);
|
||||||
|
|
||||||
for (MarkwonPlugin plugin : plugins) {
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
plugin.afterRender(node, visitor);
|
plugin.afterRender(node, visitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//noinspection UnnecessaryLocalVariable
|
||||||
final Spanned spanned = visitor.builder().spannableStringBuilder();
|
final Spanned spanned = visitor.builder().spannableStringBuilder();
|
||||||
|
|
||||||
// clear render props and builder after rendering
|
// clear render props and builder after rendering
|
||||||
visitor.clear();
|
// @since 4.1.1-SNAPSHOT as we no longer reuse visitor - there is no need to clean it
|
||||||
|
// we might still do it if we introduce a thread-local storage though
|
||||||
|
// visitor.clear();
|
||||||
|
|
||||||
return spanned;
|
return spanned;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package io.noties.markwon;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 4.1.1-SNAPSHOT
|
||||||
|
*/
|
||||||
|
abstract class MarkwonVisitorFactory {
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
abstract MarkwonVisitor create();
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
static MarkwonVisitorFactory create(
|
||||||
|
@NonNull final MarkwonVisitorImpl.Builder builder,
|
||||||
|
@NonNull final MarkwonConfiguration configuration,
|
||||||
|
@NonNull final RenderProps renderProps) {
|
||||||
|
return new MarkwonVisitorFactory() {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
MarkwonVisitor create() {
|
||||||
|
return builder.build(configuration, renderProps);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -269,6 +269,11 @@ class MarkwonVisitorImpl implements MarkwonVisitor {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public <N extends Node> Builder on(@NonNull Class<N> node, @Nullable NodeVisitor<? super N> nodeVisitor) {
|
public <N extends Node> Builder on(@NonNull Class<N> node, @Nullable NodeVisitor<? super N> nodeVisitor) {
|
||||||
|
|
||||||
|
// @since 4.1.1-SNAPSHOT we might actually introduce a local flag to check if it's been built
|
||||||
|
// and throw an exception here if some modification is requested
|
||||||
|
// NB, as we might be built from different threads this flag must be synchronized
|
||||||
|
|
||||||
// we should allow `null` to exclude node from being visited (for example to disable
|
// we should allow `null` to exclude node from being visited (for example to disable
|
||||||
// some functionality)
|
// some functionality)
|
||||||
if (nodeVisitor == null) {
|
if (nodeVisitor == null) {
|
||||||
|
@ -31,6 +31,7 @@ import static org.mockito.ArgumentMatchers.eq;
|
|||||||
import static org.mockito.Mockito.RETURNS_MOCKS;
|
import static org.mockito.Mockito.RETURNS_MOCKS;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@ -47,7 +48,7 @@ public class MarkwonImplTest {
|
|||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
null,
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
mock(MarkwonVisitor.class),
|
mock(MarkwonVisitorFactory.class),
|
||||||
Collections.singletonList(plugin));
|
Collections.singletonList(plugin));
|
||||||
|
|
||||||
impl.parse("whatever");
|
impl.parse("whatever");
|
||||||
@ -70,7 +71,7 @@ public class MarkwonImplTest {
|
|||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
null,
|
null,
|
||||||
parser,
|
parser,
|
||||||
mock(MarkwonVisitor.class),
|
mock(MarkwonVisitorFactory.class),
|
||||||
Arrays.asList(first, second));
|
Arrays.asList(first, second));
|
||||||
|
|
||||||
impl.parse("zero");
|
impl.parse("zero");
|
||||||
@ -89,6 +90,7 @@ public class MarkwonImplTest {
|
|||||||
|
|
||||||
final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
|
final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
|
||||||
|
|
||||||
|
final MarkwonVisitorFactory visitorFactory = mock(MarkwonVisitorFactory.class);
|
||||||
final MarkwonVisitor visitor = mock(MarkwonVisitor.class);
|
final MarkwonVisitor visitor = mock(MarkwonVisitor.class);
|
||||||
final SpannableBuilder builder = mock(SpannableBuilder.class);
|
final SpannableBuilder builder = mock(SpannableBuilder.class);
|
||||||
|
|
||||||
@ -96,9 +98,10 @@ public class MarkwonImplTest {
|
|||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
null,
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
visitor,
|
visitorFactory,
|
||||||
Collections.singletonList(plugin));
|
Collections.singletonList(plugin));
|
||||||
|
|
||||||
|
when(visitorFactory.create()).thenReturn(visitor);
|
||||||
when(visitor.builder()).thenReturn(builder);
|
when(visitor.builder()).thenReturn(builder);
|
||||||
|
|
||||||
final Node node = mock(Node.class);
|
final Node node = mock(Node.class);
|
||||||
@ -132,24 +135,30 @@ public class MarkwonImplTest {
|
|||||||
public void render_clears_visitor() {
|
public void render_clears_visitor() {
|
||||||
// each render call should have empty-state visitor (no previous rendering info)
|
// each render call should have empty-state visitor (no previous rendering info)
|
||||||
|
|
||||||
|
final MarkwonVisitorFactory visitorFactory = mock(MarkwonVisitorFactory.class);
|
||||||
final MarkwonVisitor visitor = mock(MarkwonVisitor.class, RETURNS_MOCKS);
|
final MarkwonVisitor visitor = mock(MarkwonVisitor.class, RETURNS_MOCKS);
|
||||||
|
|
||||||
|
when(visitorFactory.create()).thenReturn(visitor);
|
||||||
|
|
||||||
final MarkwonImpl impl = new MarkwonImpl(
|
final MarkwonImpl impl = new MarkwonImpl(
|
||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
null,
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
visitor,
|
visitorFactory,
|
||||||
Collections.<MarkwonPlugin>emptyList());
|
Collections.<MarkwonPlugin>emptyList());
|
||||||
|
|
||||||
impl.render(mock(Node.class));
|
impl.render(mock(Node.class));
|
||||||
|
|
||||||
verify(visitor, times(1)).clear();
|
// obsolete starting with 4.1.1-SNAPSHOT
|
||||||
|
// verify(visitor, times(1)).clear();
|
||||||
|
verify(visitor, never()).clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void render_props() {
|
public void render_props() {
|
||||||
// render props are configured properly and cleared after render function
|
// render props are configured properly and cleared after render function
|
||||||
|
|
||||||
|
final MarkwonVisitorFactory visitorFactory = mock(MarkwonVisitorFactory.class);
|
||||||
final MarkwonVisitor visitor = mock(MarkwonVisitor.class, RETURNS_MOCKS);
|
final MarkwonVisitor visitor = mock(MarkwonVisitor.class, RETURNS_MOCKS);
|
||||||
|
|
||||||
final RenderProps renderProps = mock(RenderProps.class);
|
final RenderProps renderProps = mock(RenderProps.class);
|
||||||
@ -161,6 +170,7 @@ public class MarkwonImplTest {
|
|||||||
}
|
}
|
||||||
}).when(visitor).clear();
|
}).when(visitor).clear();
|
||||||
|
|
||||||
|
when(visitorFactory.create()).thenReturn(visitor);
|
||||||
when(visitor.renderProps()).thenReturn(renderProps);
|
when(visitor.renderProps()).thenReturn(renderProps);
|
||||||
|
|
||||||
final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
|
final MarkwonPlugin plugin = mock(MarkwonPlugin.class);
|
||||||
@ -169,7 +179,7 @@ public class MarkwonImplTest {
|
|||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
null,
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
visitor,
|
visitorFactory,
|
||||||
Collections.singletonList(plugin));
|
Collections.singletonList(plugin));
|
||||||
|
|
||||||
final AtomicBoolean flag = new AtomicBoolean(false);
|
final AtomicBoolean flag = new AtomicBoolean(false);
|
||||||
@ -191,7 +201,9 @@ public class MarkwonImplTest {
|
|||||||
|
|
||||||
assertTrue(flag.get());
|
assertTrue(flag.get());
|
||||||
|
|
||||||
verify(renderProps, times(1)).clearAll();
|
// obsolete starting with 4.1.1-SNAPSHOT
|
||||||
|
// verify(renderProps, times(1)).clearAll();
|
||||||
|
verify(renderProps, never()).clearAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -205,7 +217,7 @@ public class MarkwonImplTest {
|
|||||||
TextView.BufferType.EDITABLE,
|
TextView.BufferType.EDITABLE,
|
||||||
null,
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
mock(MarkwonVisitor.class, RETURNS_MOCKS),
|
mock(MarkwonVisitorFactory.class, RETURNS_MOCKS),
|
||||||
Collections.singletonList(plugin));
|
Collections.singletonList(plugin));
|
||||||
|
|
||||||
final TextView textView = mock(TextView.class);
|
final TextView textView = mock(TextView.class);
|
||||||
@ -252,7 +264,7 @@ public class MarkwonImplTest {
|
|||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
null,
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
mock(MarkwonVisitor.class),
|
mock(MarkwonVisitorFactory.class),
|
||||||
plugins);
|
plugins);
|
||||||
|
|
||||||
assertTrue("First", impl.hasPlugin(First.class));
|
assertTrue("First", impl.hasPlugin(First.class));
|
||||||
@ -274,7 +286,7 @@ public class MarkwonImplTest {
|
|||||||
TextView.BufferType.EDITABLE,
|
TextView.BufferType.EDITABLE,
|
||||||
textSetter,
|
textSetter,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
mock(MarkwonVisitor.class),
|
mock(MarkwonVisitorFactory.class),
|
||||||
Collections.singletonList(plugin));
|
Collections.singletonList(plugin));
|
||||||
|
|
||||||
final TextView textView = mock(TextView.class);
|
final TextView textView = mock(TextView.class);
|
||||||
@ -317,7 +329,8 @@ public class MarkwonImplTest {
|
|||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
null,
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
mock(MarkwonVisitor.class), plugins);
|
mock(MarkwonVisitorFactory.class),
|
||||||
|
plugins);
|
||||||
|
|
||||||
// should be returned
|
// should be returned
|
||||||
assertNotNull(impl.requirePlugin(MarkwonPlugin.class));
|
assertNotNull(impl.requirePlugin(MarkwonPlugin.class));
|
||||||
@ -346,7 +359,7 @@ public class MarkwonImplTest {
|
|||||||
TextView.BufferType.SPANNABLE,
|
TextView.BufferType.SPANNABLE,
|
||||||
null,
|
null,
|
||||||
mock(Parser.class),
|
mock(Parser.class),
|
||||||
mock(MarkwonVisitor.class),
|
mock(MarkwonVisitorFactory.class),
|
||||||
plugins);
|
plugins);
|
||||||
|
|
||||||
final List<? extends MarkwonPlugin> list = impl.getPlugins();
|
final List<? extends MarkwonPlugin> list = impl.getPlugins();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user