Skip to content

Commit

Permalink
Added context object to support customization of content handler
Browse files Browse the repository at this point in the history
  • Loading branch information
benfortuna committed Jul 28, 2021
1 parent fe1ca82 commit b95ef55
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 81 deletions.
16 changes: 6 additions & 10 deletions src/main/java/net/fortuna/ical4j/data/CalendarBuilder.java
Expand Up @@ -38,9 +38,8 @@
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.List;
import java.nio.charset.StandardCharsets;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
* Parses and builds an iCalendar model from an input stream. Note that this class is not thread-safe.
Expand All @@ -56,7 +55,7 @@
*/
public class CalendarBuilder implements Consumer<Calendar> {

private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

private final CalendarParser parser;

Expand Down Expand Up @@ -122,23 +121,20 @@ public CalendarBuilder(CalendarParser parser, TimeZoneRegistry tzRegistry) {
public CalendarBuilder(CalendarParser parser, PropertyFactoryRegistry propertyFactoryRegistry,
ParameterFactoryRegistry parameterFactoryRegistry, TimeZoneRegistry tzRegistry) {

this(parser, parameterFactoryRegistry, propertyFactoryRegistry, new DefaultComponentFactorySupplier(),
tzRegistry);
this(parser, new ContentHandlerContext().withParameterFactorySupplier(parameterFactoryRegistry)
.withPropertyFactorySupplier(propertyFactoryRegistry), tzRegistry);
}

/**
* @param parser a custom calendar parser
* @param tzRegistry a custom timezone registry
*/
public CalendarBuilder(CalendarParser parser, Supplier<List<ParameterFactory<?>>> parameterFactorySupplier,
Supplier<List<PropertyFactory<?>>> propertyFactorySupplier,
Supplier<List<ComponentFactory<?>>> componentFactorySupplier,
public CalendarBuilder(CalendarParser parser, ContentHandlerContext contentHandlerContext,
TimeZoneRegistry tzRegistry) {

this.parser = parser;
this.tzRegistry = tzRegistry;
this.contentHandler = new DefaultContentHandler(this, tzRegistry, parameterFactorySupplier,
propertyFactorySupplier, componentFactorySupplier);
this.contentHandler = new DefaultContentHandler(this, tzRegistry, contentHandlerContext);
}

@Override
Expand Down
75 changes: 75 additions & 0 deletions src/main/java/net/fortuna/ical4j/data/ContentHandlerContext.java
@@ -0,0 +1,75 @@
package net.fortuna.ical4j.data;

import net.fortuna.ical4j.model.ComponentFactory;
import net.fortuna.ical4j.model.ParameterFactory;
import net.fortuna.ical4j.model.PropertyFactory;

import java.util.Collections;
import java.util.List;
import java.util.function.Supplier;

/**
* Customize behaviour of {@link ContentHandler} implementations.
*/
public class ContentHandlerContext {

private Supplier<List<ParameterFactory<?>>> parameterFactorySupplier = new DefaultParameterFactorySupplier();

private Supplier<List<PropertyFactory<?>>> propertyFactorySupplier = new DefaultPropertyFactorySupplier();

private Supplier<List<ComponentFactory<?>>> componentFactorySupplier = new DefaultComponentFactorySupplier();

private List<String> ignoredPropertyNames = Collections.emptyList();

public ContentHandlerContext withParameterFactorySupplier(Supplier<List<ParameterFactory<?>>> parameterFactorySupplier) {
ContentHandlerContext context = new ContentHandlerContext();
context.parameterFactorySupplier = parameterFactorySupplier;
context.propertyFactorySupplier = this.propertyFactorySupplier;
context.componentFactorySupplier = this.componentFactorySupplier;
context.ignoredPropertyNames = this.ignoredPropertyNames;
return context;
}

public ContentHandlerContext withPropertyFactorySupplier(Supplier<List<PropertyFactory<?>>> propertyFactorySupplier) {
ContentHandlerContext context = new ContentHandlerContext();
context.parameterFactorySupplier = this.parameterFactorySupplier;
context.propertyFactorySupplier = propertyFactorySupplier;
context.componentFactorySupplier = this.componentFactorySupplier;
context.ignoredPropertyNames = this.ignoredPropertyNames;
return context;
}

public ContentHandlerContext withComponentFactorySupplier(Supplier<List<ComponentFactory<?>>> componentFactorySupplier) {
ContentHandlerContext context = new ContentHandlerContext();
context.parameterFactorySupplier = this.parameterFactorySupplier;
context.propertyFactorySupplier = this.propertyFactorySupplier;
context.componentFactorySupplier = componentFactorySupplier;
context.ignoredPropertyNames = this.ignoredPropertyNames;
return context;
}

public ContentHandlerContext withIgnoredPropertyNames(List<String> ignoredPropertyNames) {
ContentHandlerContext context = new ContentHandlerContext();
context.parameterFactorySupplier = this.parameterFactorySupplier;
context.propertyFactorySupplier = this.propertyFactorySupplier;
context.componentFactorySupplier = this.componentFactorySupplier;
context.ignoredPropertyNames = ignoredPropertyNames;
return context;
}

public Supplier<List<ParameterFactory<?>>> getParameterFactorySupplier() {
return parameterFactorySupplier;
}

public Supplier<List<PropertyFactory<?>>> getPropertyFactorySupplier() {
return propertyFactorySupplier;
}

public Supplier<List<ComponentFactory<?>>> getComponentFactorySupplier() {
return componentFactorySupplier;
}

public List<String> getIgnoredPropertyNames() {
return ignoredPropertyNames;
}
}
83 changes: 40 additions & 43 deletions src/main/java/net/fortuna/ical4j/data/DefaultContentHandler.java
Expand Up @@ -17,17 +17,12 @@
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class DefaultContentHandler implements ContentHandler {

private static final Logger LOG = LoggerFactory.getLogger(DefaultContentHandler.class);

private final Supplier<List<ParameterFactory<?>>> parameterFactorySupplier;

private final Supplier<List<PropertyFactory<?>>> propertyFactorySupplier;

private final Supplier<List<ComponentFactory<?>>> componentFactorySupplier;
private final ContentHandlerContext context;

private final TimeZoneRegistry tzRegistry;

Expand All @@ -47,20 +42,15 @@ public class DefaultContentHandler implements ContentHandler {
private Calendar calendar;

public DefaultContentHandler(Consumer<Calendar> consumer, TimeZoneRegistry tzRegistry) {
this(consumer, tzRegistry, new DefaultParameterFactorySupplier(), new DefaultPropertyFactorySupplier(),
new DefaultComponentFactorySupplier());
this(consumer, tzRegistry, new ContentHandlerContext());
}

public DefaultContentHandler(Consumer<Calendar> consumer, TimeZoneRegistry tzRegistry,
Supplier<List<ParameterFactory<?>>> parameterFactorySupplier,
Supplier<List<PropertyFactory<?>>> propertyFactorySupplier,
Supplier<List<ComponentFactory<?>>> componentFactorySupplier) {
ContentHandlerContext context) {

this.consumer = consumer;
this.tzRegistry = tzRegistry;
this.parameterFactorySupplier = parameterFactorySupplier;
this.propertyFactorySupplier = propertyFactorySupplier;
this.componentFactorySupplier = componentFactorySupplier;
this.context = context;
}

public ComponentBuilder<CalendarComponent> getComponentBuilder() {
Expand Down Expand Up @@ -94,7 +84,8 @@ public void startComponent(String name) {
throw new RuntimeException("Components nested too deep");
}

ComponentBuilder<CalendarComponent> componentBuilder = new ComponentBuilder<>(componentFactorySupplier.get());
ComponentBuilder<CalendarComponent> componentBuilder = new ComponentBuilder<>(
context.getComponentFactorySupplier().get());
componentBuilder.name(name);
components.push(componentBuilder);
}
Expand Down Expand Up @@ -126,49 +117,55 @@ public void endComponent(String name) {

@Override
public void startProperty(String name) {
propertyBuilder = new PropertyBuilder(propertyFactorySupplier.get()).name(name);
propertyHasTzId = false;
if (!context.getIgnoredPropertyNames().contains(name.toUpperCase())) {
propertyBuilder = new PropertyBuilder(context.getPropertyFactorySupplier().get()).name(name);
propertyHasTzId = false;
} else {
propertyBuilder = null;
}
}

@Override
public void propertyValue(String value) {
propertyBuilder.value(value);
if (propertyBuilder != null) {
propertyBuilder.value(value);
}
}

@Override
public void endProperty(String name) throws URISyntaxException, ParseException, IOException {
assertProperty(propertyBuilder);
Property property = propertyBuilder.build();
if (!context.getIgnoredPropertyNames().contains(name.toUpperCase())) {
assertProperty(propertyBuilder);
Property property = propertyBuilder.build();

if (propertyHasTzId) {
propertiesWithTzId.add(property);
}
// replace with a constant instance if applicable..
property = Constants.forProperty(property);
if (getComponentBuilder() != null) {
getComponentBuilder().property(property);
} else if (calendar != null) {
calendar.getProperties().add(property);
if (propertyHasTzId) {
propertiesWithTzId.add(property);
}
// replace with a constant instance if applicable..
property = Constants.forProperty(property);
if (getComponentBuilder() != null) {
getComponentBuilder().property(property);
} else if (calendar != null) {
calendar.getProperties().add(property);
}
property = null;
}

property = null;
}

@Override
public void parameter(String name, String value) throws URISyntaxException {
assertProperty(propertyBuilder);

Parameter parameter = new ParameterBuilder(parameterFactorySupplier.get())
.name(name).value(value).build();

if (parameter instanceof TzId && tzRegistry != null) {
// VTIMEZONE may be defined later, so so keep
// track of dates until all components have been
// parsed, and then try again later
propertyHasTzId = true;
if (propertyBuilder != null) {
Parameter parameter = new ParameterBuilder(context.getParameterFactorySupplier().get())
.name(name).value(value).build();

if (parameter instanceof TzId && tzRegistry != null) {
// VTIMEZONE may be defined later, so so keep
// track of dates until all components have been
// parsed, and then try again later
propertyHasTzId = true;
}
propertyBuilder.parameter(parameter);
}

propertyBuilder.parameter(parameter);
}

private void assertComponent(ComponentBuilder<?> component) {
Expand Down
12 changes: 1 addition & 11 deletions src/main/java/net/fortuna/ical4j/model/PropertyBuilder.java
Expand Up @@ -18,25 +18,18 @@ public class PropertyBuilder extends AbstractContentBuilder {

private final List<PropertyFactory<?>> factories;

private final List<String> ignoredNames;

private String name;

private String value;

private ParameterList parameters = new ParameterList();

public PropertyBuilder() {
this(Collections.emptyList(), Collections.emptyList());
this(Collections.emptyList());
}

public PropertyBuilder(List<PropertyFactory<?>> factories) {
this(factories, Collections.emptyList());
}

public PropertyBuilder(List<PropertyFactory<?>> factories, List<String> ignoredNames) {
this.factories = factories;
this.ignoredNames = ignoredNames;
}

public PropertyBuilder name(String name) {
Expand All @@ -57,9 +50,6 @@ public PropertyBuilder parameter(Parameter parameter) {
}

public Property build() throws ParseException, IOException, URISyntaxException {
if (ignoredNames.contains(name)) {
throw new IllegalArgumentException("Unsupported property name: " + name);
}
Property property = null;
String decodedValue;
try {
Expand Down
Expand Up @@ -14,7 +14,8 @@ class DefaultContentHandlerTest extends Specification {
def result

and: 'a content handler'
DefaultContentHandler contentHandler = [{result = it} as Consumer<Calendar>, TimeZoneRegistryFactory.instance.createRegistry()]
DefaultContentHandler contentHandler = [{result = it} as Consumer<Calendar>,
TimeZoneRegistryFactory.instance.createRegistry()]

when: 'a sequence of method calls to simulate calendar parsing are processed'
contentHandler.startCalendar()
Expand All @@ -35,4 +36,37 @@ class DefaultContentHandlerTest extends Specification {
}
}
}

def 'test ignored property names'() {
given: 'a calendar reference'
def result

and: 'a content handler instance with ignored properties'
DefaultContentHandler contentHandler = [{result = it} as Consumer<Calendar>,
TimeZoneRegistryFactory.instance.createRegistry(),
new ContentHandlerContext().withIgnoredPropertyNames(['DTEND'])]

when: 'a calendar is parsed'
contentHandler.startCalendar()
contentHandler.startComponent('vevent')
contentHandler.startProperty('dtstart')
contentHandler.parameter('value', 'DATE')
contentHandler.propertyValue('20181212')
contentHandler.endProperty('dtstart')
contentHandler.startProperty('dtend')
contentHandler.parameter('value', 'DATE')
contentHandler.propertyValue('20181213')
contentHandler.endProperty('dtend')
contentHandler.endComponent('vevent')
contentHandler.endCalendar()

then: 'the resulting calendar doesn\'t include ignored properties'
result == new ContentBuilder().with {
calendar {
vevent {
dtstart '20181212', parameters: parameters { value 'DATE' }
}
}
}
}
}
Expand Up @@ -33,20 +33,6 @@ class PropertyBuilderTest extends Specification {
thrown(IllegalArgumentException)
}

def 'test build ignored property'() {
given: 'a property builder instance'
PropertyBuilder builder = [[], ['DTEND']]

and: 'builder is initialised'
builder.name('DTEND').value('20150403')

when: 'build method called'
Property p = builder.build()

then: 'an exception is thrown'
thrown(IllegalArgumentException)
}

def 'test build encoded property'() {
given: 'a property builder instance'
PropertyBuilder builder = []
Expand Down
Expand Up @@ -11,9 +11,12 @@ import net.fortuna.ical4j.model.TimeZoneRegistryFactory
class FeatureTest extends AbstractBuilderTest {

def setup() {
ContentHandlerContext contentHandlerContext = new ContentHandlerContext()
.withParameterFactorySupplier(new ServiceLoaderParameterFactorySupplier())
.withPropertyFactorySupplier(new ServiceLoaderPropertyFactorySupplier())
.withComponentFactorySupplier(new ServiceLoaderComponentFactorySupplier())
builder = new CalendarBuilder(CalendarParserFactory.getInstance().get(),
new ServiceLoaderParameterFactorySupplier(), new ServiceLoaderPropertyFactorySupplier(),
new ServiceLoaderComponentFactorySupplier(), TimeZoneRegistryFactory.getInstance().createRegistry());
contentHandlerContext, TimeZoneRegistryFactory.getInstance().createRegistry());
}

def 'assert value stored correctly'() {
Expand Down

0 comments on commit b95ef55

Please sign in to comment.