diff --git a/src/main/java/net/fortuna/ical4j/data/CalendarBuilder.java b/src/main/java/net/fortuna/ical4j/data/CalendarBuilder.java index 11fab4c28..e3c70d802 100644 --- a/src/main/java/net/fortuna/ical4j/data/CalendarBuilder.java +++ b/src/main/java/net/fortuna/ical4j/data/CalendarBuilder.java @@ -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. @@ -56,7 +55,7 @@ */ public class CalendarBuilder implements Consumer { - private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private final CalendarParser parser; @@ -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>> parameterFactorySupplier, - Supplier>> propertyFactorySupplier, - Supplier>> 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 diff --git a/src/main/java/net/fortuna/ical4j/data/ContentHandlerContext.java b/src/main/java/net/fortuna/ical4j/data/ContentHandlerContext.java new file mode 100644 index 000000000..b16276401 --- /dev/null +++ b/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>> parameterFactorySupplier = new DefaultParameterFactorySupplier(); + + private Supplier>> propertyFactorySupplier = new DefaultPropertyFactorySupplier(); + + private Supplier>> componentFactorySupplier = new DefaultComponentFactorySupplier(); + + private List ignoredPropertyNames = Collections.emptyList(); + + public ContentHandlerContext withParameterFactorySupplier(Supplier>> 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>> 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>> 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 ignoredPropertyNames) { + ContentHandlerContext context = new ContentHandlerContext(); + context.parameterFactorySupplier = this.parameterFactorySupplier; + context.propertyFactorySupplier = this.propertyFactorySupplier; + context.componentFactorySupplier = this.componentFactorySupplier; + context.ignoredPropertyNames = ignoredPropertyNames; + return context; + } + + public Supplier>> getParameterFactorySupplier() { + return parameterFactorySupplier; + } + + public Supplier>> getPropertyFactorySupplier() { + return propertyFactorySupplier; + } + + public Supplier>> getComponentFactorySupplier() { + return componentFactorySupplier; + } + + public List getIgnoredPropertyNames() { + return ignoredPropertyNames; + } +} diff --git a/src/main/java/net/fortuna/ical4j/data/DefaultContentHandler.java b/src/main/java/net/fortuna/ical4j/data/DefaultContentHandler.java index 365eac8e0..75fe4f1aa 100644 --- a/src/main/java/net/fortuna/ical4j/data/DefaultContentHandler.java +++ b/src/main/java/net/fortuna/ical4j/data/DefaultContentHandler.java @@ -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>> parameterFactorySupplier; - - private final Supplier>> propertyFactorySupplier; - - private final Supplier>> componentFactorySupplier; + private final ContentHandlerContext context; private final TimeZoneRegistry tzRegistry; @@ -47,20 +42,15 @@ public class DefaultContentHandler implements ContentHandler { private Calendar calendar; public DefaultContentHandler(Consumer consumer, TimeZoneRegistry tzRegistry) { - this(consumer, tzRegistry, new DefaultParameterFactorySupplier(), new DefaultPropertyFactorySupplier(), - new DefaultComponentFactorySupplier()); + this(consumer, tzRegistry, new ContentHandlerContext()); } public DefaultContentHandler(Consumer consumer, TimeZoneRegistry tzRegistry, - Supplier>> parameterFactorySupplier, - Supplier>> propertyFactorySupplier, - Supplier>> componentFactorySupplier) { + ContentHandlerContext context) { this.consumer = consumer; this.tzRegistry = tzRegistry; - this.parameterFactorySupplier = parameterFactorySupplier; - this.propertyFactorySupplier = propertyFactorySupplier; - this.componentFactorySupplier = componentFactorySupplier; + this.context = context; } public ComponentBuilder getComponentBuilder() { @@ -94,7 +84,8 @@ public void startComponent(String name) { throw new RuntimeException("Components nested too deep"); } - ComponentBuilder componentBuilder = new ComponentBuilder<>(componentFactorySupplier.get()); + ComponentBuilder componentBuilder = new ComponentBuilder<>( + context.getComponentFactorySupplier().get()); componentBuilder.name(name); components.push(componentBuilder); } @@ -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) { diff --git a/src/main/java/net/fortuna/ical4j/model/PropertyBuilder.java b/src/main/java/net/fortuna/ical4j/model/PropertyBuilder.java index 8d07b23f4..713d75ba0 100644 --- a/src/main/java/net/fortuna/ical4j/model/PropertyBuilder.java +++ b/src/main/java/net/fortuna/ical4j/model/PropertyBuilder.java @@ -18,8 +18,6 @@ public class PropertyBuilder extends AbstractContentBuilder { private final List> factories; - private final List ignoredNames; - private String name; private String value; @@ -27,16 +25,11 @@ public class PropertyBuilder extends AbstractContentBuilder { private ParameterList parameters = new ParameterList(); public PropertyBuilder() { - this(Collections.emptyList(), Collections.emptyList()); + this(Collections.emptyList()); } public PropertyBuilder(List> factories) { - this(factories, Collections.emptyList()); - } - - public PropertyBuilder(List> factories, List ignoredNames) { this.factories = factories; - this.ignoredNames = ignoredNames; } public PropertyBuilder name(String name) { @@ -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 { diff --git a/src/test/groovy/net/fortuna/ical4j/data/DefaultContentHandlerTest.groovy b/src/test/groovy/net/fortuna/ical4j/data/DefaultContentHandlerTest.groovy index 0a8db9d8b..ed9290f78 100644 --- a/src/test/groovy/net/fortuna/ical4j/data/DefaultContentHandlerTest.groovy +++ b/src/test/groovy/net/fortuna/ical4j/data/DefaultContentHandlerTest.groovy @@ -14,7 +14,8 @@ class DefaultContentHandlerTest extends Specification { def result and: 'a content handler' - DefaultContentHandler contentHandler = [{result = it} as Consumer, TimeZoneRegistryFactory.instance.createRegistry()] + DefaultContentHandler contentHandler = [{result = it} as Consumer, + TimeZoneRegistryFactory.instance.createRegistry()] when: 'a sequence of method calls to simulate calendar parsing are processed' contentHandler.startCalendar() @@ -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, + 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' } + } + } + } + } } diff --git a/src/test/groovy/net/fortuna/ical4j/model/PropertyBuilderTest.groovy b/src/test/groovy/net/fortuna/ical4j/model/PropertyBuilderTest.groovy index e246a6322..127a6ecad 100644 --- a/src/test/groovy/net/fortuna/ical4j/model/PropertyBuilderTest.groovy +++ b/src/test/groovy/net/fortuna/ical4j/model/PropertyBuilderTest.groovy @@ -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 = [] diff --git a/src/test/groovy/net/fortuna/ical4j/model/parameter/FeatureTest.groovy b/src/test/groovy/net/fortuna/ical4j/model/parameter/FeatureTest.groovy index 3cd6be464..593cc624a 100644 --- a/src/test/groovy/net/fortuna/ical4j/model/parameter/FeatureTest.groovy +++ b/src/test/groovy/net/fortuna/ical4j/model/parameter/FeatureTest.groovy @@ -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'() {