diff --git a/src/main/java/com/apptasticsoftware/rssreader/module/georss/Coordinate.java b/src/main/java/com/apptasticsoftware/rssreader/module/georss/Coordinate.java new file mode 100644 index 00000000..4668f65c --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/module/georss/Coordinate.java @@ -0,0 +1,88 @@ +package com.apptasticsoftware.rssreader.module.georss; + +import java.util.Objects; + +/** + * Represents a geographic coordinate with latitude and longitude. + */ +public class Coordinate { + private double latitude; + private double longitude; + + /** + * Constructs a Coordinate with default values. + */ + public Coordinate() { + + } + + /** + * Constructs a Coordinate with the specified latitude and longitude. + * + * @param latitude the latitude value + * @param longitude the longitude value + */ + public Coordinate(double latitude, double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } + + /** + * Returns the latitude value. + * + * @return the latitude + */ + public double getLatitude() { + return latitude; + } + + /** + * Sets the latitude value. + * + * @param latitude the latitude to set + */ + public void setLatitude(double latitude) { + this.latitude = latitude; + } + + /** + * Returns the longitude value. + * + * @return the longitude + */ + public double getLongitude() { + return longitude; + } + + /** + * Sets the longitude value. + * + * @param longitude the longitude to set + */ + public void setLongitude(double longitude) { + this.longitude = longitude; + } + + /** + * Compares this coordinate with another object for equality. + * + * @param o the object to compare with + * @return true if both coordinates have equal latitude and longitude + */ + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + Coordinate that = (Coordinate) o; + return Double.compare(getLatitude(), that.getLatitude()) == 0 && Double.compare(getLongitude(), that.getLongitude()) == 0; + } + + /** + * Returns the hash code for this coordinate. + * + * @return the hash code + */ + @Override + public int hashCode() { + return Objects.hash(getLatitude(), getLongitude()); + } +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssChannel.java b/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssChannel.java new file mode 100644 index 00000000..e6cdf51a --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssChannel.java @@ -0,0 +1,16 @@ +package com.apptasticsoftware.rssreader.module.georss; + +import com.apptasticsoftware.rssreader.Channel; + +/** + * Represents a GeoRSS channel with geographic extension data. + */ +public interface GeoRssChannel extends Channel, GeoRssChannelData { + + /** + * Returns the GeoRSS channel data. + * + * @return the GeoRSS channel data + */ + GeoRssChannelData getGeoRssChannelData(); +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssChannelData.java b/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssChannelData.java new file mode 100644 index 00000000..8c98717b --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssChannelData.java @@ -0,0 +1,233 @@ +package com.apptasticsoftware.rssreader.module.georss; + +import java.util.List; +import java.util.Optional; + +/** + * Interface for GeoRSS channel data containing geographic information. + */ +public interface GeoRssChannelData { + + /** + * Returns the underlying GeoRSS channel data implementation. + * + * @return the GeoRSS channel data + */ + GeoRssChannelData getGeoRssChannelData(); + + /** + * Returns the GeoRSS point as a string. + * + * @return the GeoRSS point + */ + default Optional getGeoRssPoint() { + return getGeoRssChannelData().getGeoRssPoint(); + } + + /** + * Returns the GeoRSS point as a Coordinate. + * + * @return the coordinate + */ + default Optional getGeoRssPointAsCoordinate() { + return getGeoRssChannelData().getGeoRssPointAsCoordinate(); + } + + /** + * Sets the GeoRSS point. + * + * @param geoRssPoint the point to set + */ + default void setGeoRssPoint(String geoRssPoint) { + getGeoRssChannelData().setGeoRssPoint(geoRssPoint); + } + + /** + * Returns the GeoRSS line as a string. + * + * @return the GeoRSS line + */ + default Optional getGeoRssLine() { + return getGeoRssChannelData().getGeoRssLine(); + } + + /** + * Returns the GeoRSS line as a list of Coordinates. + * + * @return the list of coordinates + */ + default List getGeoRssLineAsCoordinates() { + return getGeoRssChannelData().getGeoRssLineAsCoordinates(); + } + + /** + * Sets the GeoRSS line. + * + * @param geoRssLine the line to set + */ + default void setGeoRssLine(String geoRssLine) { + getGeoRssChannelData().setGeoRssLine(geoRssLine); + } + + /** + * Returns the GeoRSS polygon as a string. + * + * @return the GeoRSS polygon + */ + default Optional getGeoRssPolygon() { + return getGeoRssChannelData().getGeoRssPolygon(); + } + + /** + * Returns the GeoRSS polygon as a list of Coordinates. + * + * @return the list of coordinates + */ + default List getGeoRssPolygonAsCoordinates() { + return getGeoRssChannelData().getGeoRssPolygonAsCoordinates(); + } + + /** + * Sets the GeoRSS polygon. + * + * @param geoRssPolygon the polygon to set + */ + default void setGeoRssPolygon(String geoRssPolygon) { + getGeoRssChannelData().setGeoRssPolygon(geoRssPolygon); + } + + /** + * Returns the GeoRSS box as a string. + * + * @return the GeoRSS box + */ + default Optional getGeoRssBox() { + return getGeoRssChannelData().getGeoRssBox(); + } + + /** + * Returns the GeoRSS box as a list of Coordinates. + * + * @return the list of coordinates + */ + default List getGeoRssBoxAsCoordinates() { + return getGeoRssChannelData().getGeoRssBoxAsCoordinates(); + } + + /** + * Sets the GeoRSS box. + * + * @param geoRssBox the box to set + */ + default void setGeoRssBox(String geoRssBox) { + getGeoRssChannelData().setGeoRssBox(geoRssBox); + } + + /** + * Returns the GeoRSS elevation. + * + * @return the elevation + */ + default Optional getGeoRssElevation() { + return getGeoRssChannelData().getGeoRssElevation(); + } + + /** + * Sets the GeoRSS elevation. + * + * @param geoRssElevation the elevation to set + */ + default void setGeoRssElevation(Double geoRssElevation) { + getGeoRssChannelData().setGeoRssElevation(geoRssElevation); + } + + /** + * Returns the GeoRSS floor. + * + * @return the floor + */ + default Optional getGeoRssFloor() { + return getGeoRssChannelData().getGeoRssFloor(); + } + + /** + * Sets the GeoRSS floor. + * + * @param geoRssFloor the floor to set + */ + default void setGeoRssFloor(Integer geoRssFloor) { + getGeoRssChannelData().setGeoRssFloor(geoRssFloor); + } + + /** + * Returns the GeoRSS radius. + * + * @return the radius + */ + default Optional getGeoRssRadius() { + return getGeoRssChannelData().getGeoRssRadius(); + } + + /** + * Sets the GeoRSS radius. + * + * @param geoRssRadius the radius to set + */ + default void setGeoRssRadius(Double geoRssRadius) { + getGeoRssChannelData().setGeoRssRadius(geoRssRadius); + } + + /** + * Returns the GeoRSS feature type tag. + * + * @return the feature type tag + */ + default Optional getGeoRssFeatureTypeTag() { + return getGeoRssChannelData().getGeoRssFeatureTypeTag(); + } + + /** + * Sets the GeoRSS feature type tag. + * + * @param geoRssFeatureTypeTag the feature type tag to set + */ + default void setGeoRssFeatureTypeTag(String geoRssFeatureTypeTag) { + getGeoRssChannelData().setGeoRssFeatureTypeTag(geoRssFeatureTypeTag); + } + + /** + * Returns the GeoRSS relationship tag. + * + * @return the relationship tag + */ + default Optional getGeoRssRelationshipTag() { + return getGeoRssChannelData().getGeoRssRelationshipTag(); + } + + /** + * Sets the GeoRSS relationship tag. + * + * @param geoRssRelationshipTag the relationship tag to set + */ + default void setGeoRssRelationshipTag(String geoRssRelationshipTag) { + getGeoRssChannelData().setGeoRssRelationshipTag(geoRssRelationshipTag); + } + + /** + * Returns the GeoRSS feature name. + * + * @return the feature name + */ + default Optional getGeoRssFeatureName() { + return getGeoRssChannelData().getGeoRssFeatureName(); + } + + /** + * Sets the GeoRSS feature name. + * + * @param geoRssFeatureName the feature name to set + */ + default void setGeoRssFeatureName(String geoRssFeatureName) { + getGeoRssChannelData().setGeoRssFeatureName(geoRssFeatureName); + } +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssExtensions.java b/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssExtensions.java new file mode 100644 index 00000000..17215e69 --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssExtensions.java @@ -0,0 +1,185 @@ +package com.apptasticsoftware.rssreader.module.georss; + +import com.apptasticsoftware.rssreader.FeedExtensionRegistry; + +import java.util.List; + +import static com.apptasticsoftware.rssreader.util.Mapper.mapDouble; +import static com.apptasticsoftware.rssreader.util.Mapper.mapInteger; + +/** + * Utility class for registering GeoRSS feed extensions. + */ +public class GeoRssExtensions { + private static final List CHANNEL_PATHS = List.of("/rss/channel/", "/feed/"); + private static final List ITEM_PATHS = List.of("/rss/channel/item/", "/feed/entry/"); + + private GeoRssExtensions() { + // Prevent instantiation + } + + /** + * Registers GeoRSS channel and item extensions with the provided registry. + * + * @param registry the feed extension registry + */ + public static void register(FeedExtensionRegistry registry) { + channelTagExtensions(registry); + itemTagExtensions(registry); + } + + /** + * Registers channel tag extensions. + * + * @param registry the feed extension registry + */ + private static void channelTagExtensions(FeedExtensionRegistry registry) { + registry.addChannelExtension("georss:point", GeoRssChannel::setGeoRssPoint); + registry.addChannelExtension("georss:line", GeoRssChannel::setGeoRssLine); + registry.addChannelExtension("georss:polygon", GeoRssChannel::setGeoRssPolygon); + registry.addChannelExtension("georss:box", GeoRssChannel::setGeoRssBox); + registry.addChannelExtension("georss:elev", (item, value) -> mapDouble(value, item::setGeoRssElevation)); + registry.addChannelExtension("georss:floor", (item, value) -> mapInteger(value, item::setGeoRssFloor)); + registry.addChannelExtension("georss:radius", (item, value) -> mapDouble(value, item::setGeoRssRadius)); + registry.addChannelExtension("georss:featuretypetag", GeoRssChannel::setGeoRssFeatureTypeTag); + registry.addChannelExtension("georss:relationshiptag", GeoRssChannel::setGeoRssRelationshipTag); + registry.addChannelExtension("georss:featurename", GeoRssChannel::setGeoRssFeatureName); + registry.addChannelExtension("geo:lat", GeoRssExtensions::mapLatitude); + registry.addChannelExtension("geo:long", GeoRssExtensions::mapLongitude); + + registry.addChannelExtension(CHANNEL_PATHS, "georss:where/gml:Point/gml:pos", GeoRssChannel::setGeoRssPoint); + registry.addChannelExtension(CHANNEL_PATHS, "georss:where/gml:LineString/gml:posList", GeoRssChannel::setGeoRssLine); + registry.addChannelExtension(CHANNEL_PATHS, "georss:where/gml:Polygon/gml:exterior/gml:LinearRing/gml:posList", GeoRssChannel::setGeoRssPolygon); + registry.addChannelExtension(CHANNEL_PATHS, "georss:where/gml:Envelope/gml:lowerCorner", GeoRssExtensions::mapEnvelope); + registry.addChannelExtension(CHANNEL_PATHS, "georss:where/gml:Envelope/gml:upperCorner", GeoRssExtensions::mapEnvelope); + } + + /** + * Registers item tag extensions. + * + * @param registry the feed extension registry + */ + private static void itemTagExtensions(FeedExtensionRegistry registry) { + registry.addItemExtension("georss:point", GeoRssItem::setGeoRssPoint); + registry.addItemExtension("georss:line", GeoRssItem::setGeoRssLine); + registry.addItemExtension("georss:polygon", GeoRssItem::setGeoRssPolygon); + registry.addItemExtension("georss:box", GeoRssItem::setGeoRssBox); + registry.addItemExtension("georss:elev", (item, value) -> mapDouble(value, item::setGeoRssElevation)); + registry.addItemExtension("georss:floor", (item, value) -> mapInteger(value, item::setGeoRssFloor)); + registry.addItemExtension("georss:radius", (item, value) -> mapDouble(value, item::setGeoRssRadius)); + registry.addItemExtension("georss:featuretypetag", GeoRssItem::setGeoRssFeatureTypeTag); + registry.addItemExtension("georss:relationshiptag", GeoRssItem::setGeoRssRelationshipTag); + registry.addItemExtension("georss:featurename", GeoRssItem::setGeoRssFeatureName); + registry.addItemExtension("geo:lat", GeoRssExtensions::mapLatitude); + registry.addItemExtension("geo:long", GeoRssExtensions::mapLongitude); + + registry.addItemExtension(ITEM_PATHS, "georss:where/gml:Point/gml:pos", GeoRssItem::setGeoRssPoint); + registry.addItemExtension(ITEM_PATHS, "georss:where/gml:LineString/gml:posList", GeoRssItem::setGeoRssLine); + registry.addItemExtension(ITEM_PATHS, "georss:where/gml:Polygon/gml:exterior/gml:LinearRing/gml:posList", GeoRssItemData::setGeoRssPolygon); + registry.addItemExtension(ITEM_PATHS, "georss:where/gml:Envelope/gml:lowerCorner", GeoRssExtensions::mapEnvelope); + registry.addItemExtension(ITEM_PATHS, "georss:where/gml:Envelope/gml:upperCorner", GeoRssExtensions::mapEnvelope); + } + + /** + * Maps envelope coordinates to GeoRSS box for channels. + * + * @param geoRssChannel the GeoRSS channel + * @param text the envelope text + */ + private static void mapEnvelope(GeoRssChannel geoRssChannel, String text) { + var existingValue = geoRssChannel.getGeoRssBox().orElse("").trim(); + var newValue = existingValue + " " + text.trim(); + geoRssChannel.setGeoRssBox(newValue); + } + + /** + * Maps envelope coordinates to GeoRSS box for items. + * + * @param geoRssItem the GeoRSS item + * @param text the envelope text + */ + private static void mapEnvelope(GeoRssItem geoRssItem, String text) { + var existingValue = geoRssItem.getGeoRssBox().orElse("").trim(); + var newValue = existingValue + " " + text.trim(); + geoRssItem.setGeoRssBox(newValue); + } + + /** + * Maps latitude value to GeoRSS point for channels. + * + * @param geoRssItem the GeoRSS channel + * @param text the latitude text + */ + private static void mapLatitude(GeoRssChannel geoRssItem, String text) { + var existingValue = geoRssItem.getGeoRssPoint().orElse("").trim(); + if (!existingValue.isEmpty() && containsWhitespace(existingValue)) { + return; + } + var newValue = existingValue.isEmpty() ? text.trim() : text.trim() + " " + existingValue; + geoRssItem.setGeoRssPoint(newValue); + } + + /** + * Maps longitude value to GeoRSS point for channels. + * + * @param geoRssItem the GeoRSS channel + * @param text the longitude text + */ + private static void mapLongitude(GeoRssChannel geoRssItem, String text) { + var existingValue = geoRssItem.getGeoRssPoint().orElse("").trim(); + if (!existingValue.isEmpty() && containsWhitespace(existingValue)) { + return; + } + var newValue = existingValue.isEmpty() ? text.trim() : existingValue + " " + text.trim(); + geoRssItem.setGeoRssPoint(newValue); + } + + /** + * Maps latitude value to GeoRSS point for items. + * + * @param geoRssItem the GeoRSS item + * @param text the latitude text + */ + private static void mapLatitude(GeoRssItem geoRssItem, String text) { + var existingValue = geoRssItem.getGeoRssPoint().orElse("").trim(); + if (!existingValue.isEmpty() && containsWhitespace(existingValue)) { + return; + } + var newValue = existingValue.isEmpty() ? text.trim() : text.trim() + " " + existingValue; + geoRssItem.setGeoRssPoint(newValue); + } + + /** + * Maps longitude value to GeoRSS point for items. + * + * @param geoRssItem the GeoRSS item + * @param text the longitude text + */ + private static void mapLongitude(GeoRssItem geoRssItem, String text) { + var existingValue = geoRssItem.getGeoRssPoint().orElse("").trim(); + if (!existingValue.isEmpty() && containsWhitespace(existingValue)) { + return; + } + var newValue = existingValue.isEmpty() ? text.trim() : existingValue + " " + text.trim(); + geoRssItem.setGeoRssPoint(newValue); + } + + /** + * Checks if the specified text contains whitespace characters. + * + * @param text the text to check + * @return true if text contains whitespace, false otherwise + */ + private static boolean containsWhitespace(String text) { + if (text == null) { + return false; + } + for (int i = 0; i < text.length(); i++) { + if (Character.isWhitespace(text.charAt(i))) { + return true; + } + } + return false; + } + +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssFeedReader.java b/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssFeedReader.java new file mode 100644 index 00000000..7855892f --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssFeedReader.java @@ -0,0 +1,62 @@ +package com.apptasticsoftware.rssreader.module.georss; + +import com.apptasticsoftware.rssreader.AbstractRssReader; +import com.apptasticsoftware.rssreader.DateTimeParser; +import com.apptasticsoftware.rssreader.module.georss.internal.GeoRssChannelImpl; +import com.apptasticsoftware.rssreader.module.georss.internal.GeoRssItemImpl; + +import java.net.http.HttpClient; + +/** + * RSS reader implementation supporting GeoRSS extensions. + */ +public class GeoRssFeedReader extends AbstractRssReader { + + /** + * Constructs a GeoRssFeedReader with default HTTP client. + */ + public GeoRssFeedReader() { + super(); + } + + /** + * Constructs a GeoRssFeedReader with the specified HTTP client. + * + * @param httpClient the HTTP client to use + */ + public GeoRssFeedReader(HttpClient httpClient) { + super(httpClient); + } + + /** + * Creates a GeoRSS channel implementation. + * + * @param dateTimeParser the date time parser + * @return a new GeoRSS channel + */ + @Override + protected GeoRssChannel createChannel(DateTimeParser dateTimeParser) { + return new GeoRssChannelImpl(dateTimeParser); + } + + /** + * Creates a GeoRSS item implementation. + * + * @param dateTimeParser the date time parser + * @return a new GeoRSS item + */ + @Override + protected GeoRssItem createItem(DateTimeParser dateTimeParser) { + return new GeoRssItemImpl(dateTimeParser); + } + + /** + * Registers GeoRSS channel tags and extensions. + */ + @Override + protected void registerChannelTags() { + super.registerChannelTags(); + var registry = getFeedExtensionRegistry(); + GeoRssExtensions.register(registry); + } +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssItem.java b/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssItem.java new file mode 100644 index 00000000..de616746 --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssItem.java @@ -0,0 +1,9 @@ +package com.apptasticsoftware.rssreader.module.georss; + +import com.apptasticsoftware.rssreader.Item; + +/** + * Represents a GeoRSS item with geographic extension data. + */ +public interface GeoRssItem extends Item, GeoRssItemData { +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssItemData.java b/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssItemData.java new file mode 100644 index 00000000..b8a41ef9 --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/module/georss/GeoRssItemData.java @@ -0,0 +1,233 @@ +package com.apptasticsoftware.rssreader.module.georss; + +import java.util.List; +import java.util.Optional; + +/** + * Interface for GeoRSS item data containing geographic information. + */ +public interface GeoRssItemData { + + /** + * Returns the underlying GeoRSS item data implementation. + * + * @return the GeoRSS item data + */ + GeoRssItemData getGeoRssItemData(); + + /** + * Returns the GeoRSS point as a string. + * + * @return the GeoRSS point + */ + default Optional getGeoRssPoint() { + return getGeoRssItemData().getGeoRssPoint(); + } + + /** + * Returns the GeoRSS point as a Coordinate. + * + * @return the coordinate + */ + default Optional getGeoRssPointAsCoordinate() { + return getGeoRssItemData().getGeoRssPointAsCoordinate(); + } + + /** + * Sets the GeoRSS point. + * + * @param geoRssPoint the point to set + */ + default void setGeoRssPoint(String geoRssPoint) { + getGeoRssItemData().setGeoRssPoint(geoRssPoint); + } + + /** + * Returns the GeoRSS line as a string. + * + * @return the GeoRSS line + */ + default Optional getGeoRssLine() { + return getGeoRssItemData().getGeoRssLine(); + } + + /** + * Returns the GeoRSS line as a list of Coordinates. + * + * @return the list of coordinates + */ + default List getGeoRssLineAsCoordinates() { + return getGeoRssItemData().getGeoRssLineAsCoordinates(); + } + + /** + * Sets the GeoRSS line. + * + * @param geoRssLine the line to set + */ + default void setGeoRssLine(String geoRssLine) { + getGeoRssItemData().setGeoRssLine(geoRssLine); + } + + /** + * Returns the GeoRSS polygon as a string. + * + * @return the GeoRSS polygon + */ + default Optional getGeoRssPolygon() { + return getGeoRssItemData().getGeoRssPolygon(); + } + + /** + * Returns the GeoRSS polygon as a list of Coordinates. + * + * @return the list of coordinates + */ + default List getGeoRssPolygonAsCoordinates() { + return getGeoRssItemData().getGeoRssPolygonAsCoordinates(); + } + + /** + * Sets the GeoRSS polygon. + * + * @param geoRssPolygon the polygon to set + */ + default void setGeoRssPolygon(String geoRssPolygon) { + getGeoRssItemData().setGeoRssPolygon(geoRssPolygon); + } + + /** + * Returns the GeoRSS box as a string. + * + * @return the GeoRSS box + */ + default Optional getGeoRssBox() { + return getGeoRssItemData().getGeoRssBox(); + } + + /** + * Returns the GeoRSS box as a list of Coordinates. + * + * @return the list of coordinates + */ + default List getGeoRssBoxAsCoordinates() { + return getGeoRssItemData().getGeoRssBoxAsCoordinates(); + } + + /** + * Sets the GeoRSS box. + * + * @param geoRssBox the box to set + */ + default void setGeoRssBox(String geoRssBox) { + getGeoRssItemData().setGeoRssBox(geoRssBox); + } + + /** + * Returns the GeoRSS elevation. + * + * @return the elevation + */ + default Optional getGeoRssElevation() { + return getGeoRssItemData().getGeoRssElevation(); + } + + /** + * Sets the GeoRSS elevation. + * + * @param geoRssElevation the elevation to set + */ + default void setGeoRssElevation(Double geoRssElevation) { + getGeoRssItemData().setGeoRssElevation(geoRssElevation); + } + + /** + * Returns the GeoRSS floor. + * + * @return the floor + */ + default Optional getGeoRssFloor() { + return getGeoRssItemData().getGeoRssFloor(); + } + + /** + * Sets the GeoRSS floor. + * + * @param geoRssFloor the floor to set + */ + default void setGeoRssFloor(Integer geoRssFloor) { + getGeoRssItemData().setGeoRssFloor(geoRssFloor); + } + + /** + * Returns the GeoRSS radius. + * + * @return the radius + */ + default Optional getGeoRssRadius() { + return getGeoRssItemData().getGeoRssRadius(); + } + + /** + * Sets the GeoRSS radius. + * + * @param geoRssRadius the radius to set + */ + default void setGeoRssRadius(Double geoRssRadius) { + getGeoRssItemData().setGeoRssRadius(geoRssRadius); + } + + /** + * Returns the GeoRSS feature type tag. + * + * @return the feature type tag + */ + default Optional getGeoRssFeatureTypeTag() { + return getGeoRssItemData().getGeoRssFeatureTypeTag(); + } + + /** + * Sets the GeoRSS feature type tag. + * + * @param geoRssFeatureTypeTag the feature type tag to set + */ + default void setGeoRssFeatureTypeTag(String geoRssFeatureTypeTag) { + getGeoRssItemData().setGeoRssFeatureTypeTag(geoRssFeatureTypeTag); + } + + /** + * Returns the GeoRSS relationship tag. + * + * @return the relationship tag + */ + default Optional getGeoRssRelationshipTag() { + return getGeoRssItemData().getGeoRssRelationshipTag(); + } + + /** + * Sets the GeoRSS relationship tag. + * + * @param geoRssRelationshipTag the relationship tag to set + */ + default void setGeoRssRelationshipTag(String geoRssRelationshipTag) { + getGeoRssItemData().setGeoRssRelationshipTag(geoRssRelationshipTag); + } + + /** + * Returns the GeoRSS feature name. + * + * @return the feature name + */ + default Optional getGeoRssFeatureName() { + return getGeoRssItemData().getGeoRssFeatureName(); + } + + /** + * Sets the GeoRSS feature name. + * + * @param geoRssFeatureName the feature name to set + */ + default void setGeoRssFeatureName(String geoRssFeatureName) { + getGeoRssItemData().setGeoRssFeatureName(geoRssFeatureName); + } +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/GeoRssChannelDataImpl.java b/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/GeoRssChannelDataImpl.java new file mode 100644 index 00000000..202f2816 --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/GeoRssChannelDataImpl.java @@ -0,0 +1,18 @@ +package com.apptasticsoftware.rssreader.module.georss.internal; + +import com.apptasticsoftware.rssreader.module.georss.GeoRssChannelData; + +/** + * Implementation of GeoRssChannelData containing GeoRSS channel geographic information. + */ +public class GeoRssChannelDataImpl extends MetaData implements GeoRssChannelData { + + /** + * Returns the underlying GeoRSS channel data. + * + * @return this instance + */ + public GeoRssChannelData getGeoRssChannelData() { + return this; + } +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/GeoRssChannelImpl.java b/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/GeoRssChannelImpl.java new file mode 100644 index 00000000..1c807b58 --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/GeoRssChannelImpl.java @@ -0,0 +1,58 @@ +package com.apptasticsoftware.rssreader.module.georss.internal; + +import com.apptasticsoftware.rssreader.DateTimeParser; +import com.apptasticsoftware.rssreader.internal.ChannelImpl; +import com.apptasticsoftware.rssreader.module.georss.GeoRssChannel; +import com.apptasticsoftware.rssreader.module.georss.GeoRssChannelData; + +import java.util.Objects; + +/** + * Implementation of GeoRssChannel with geographic extension support. + */ +public class GeoRssChannelImpl extends ChannelImpl implements GeoRssChannel { + private final GeoRssChannelData geoRssData = new GeoRssChannelDataImpl(); + + /** + * Constructs a GeoRssChannelImpl with the specified date time parser. + * + * @param dateTimeParser the date time parser + */ + public GeoRssChannelImpl(DateTimeParser dateTimeParser) { + super(dateTimeParser); + } + + /** + * Returns the GeoRSS channel data. + * + * @return the GeoRSS channel data + */ + @Override + public GeoRssChannelData getGeoRssChannelData() { + return geoRssData; + } + + /** + * Compares this channel with another object for equality. + * + * @param o the object to compare with + * @return true if both channels are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof GeoRssChannelImpl)) return false; + if (!super.equals(o)) return false; + GeoRssChannelImpl that = (GeoRssChannelImpl) o; + return Objects.equals(getGeoRssChannelData(), that.getGeoRssChannelData()); + } + + /** + * Returns the hash code for this channel. + * + * @return the hash code + */ + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), getGeoRssChannelData()); + } +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/GeoRssItemDataImpl.java b/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/GeoRssItemDataImpl.java new file mode 100644 index 00000000..513f2177 --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/GeoRssItemDataImpl.java @@ -0,0 +1,19 @@ +package com.apptasticsoftware.rssreader.module.georss.internal; + +import com.apptasticsoftware.rssreader.module.georss.GeoRssItemData; + +/** + * Implementation of GeoRssItemData containing GeoRSS item geographic information. + */ +public class GeoRssItemDataImpl extends MetaData implements GeoRssItemData { + + /** + * Returns the underlying GeoRSS item data. + * + * @return this instance + */ + @Override + public GeoRssItemData getGeoRssItemData() { + return this; + } +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/GeoRssItemImpl.java b/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/GeoRssItemImpl.java new file mode 100644 index 00000000..ceba2c1c --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/GeoRssItemImpl.java @@ -0,0 +1,58 @@ +package com.apptasticsoftware.rssreader.module.georss.internal; + +import com.apptasticsoftware.rssreader.DateTimeParser; +import com.apptasticsoftware.rssreader.internal.ItemImpl; +import com.apptasticsoftware.rssreader.module.georss.GeoRssItem; +import com.apptasticsoftware.rssreader.module.georss.GeoRssItemData; + +import java.util.Objects; + +/** + * Implementation of GeoRssItem with geographic extension support. + */ +public class GeoRssItemImpl extends ItemImpl implements GeoRssItem { + private final GeoRssItemDataImpl geoRssData = new GeoRssItemDataImpl(); + + /** + * Constructs a GeoRssItemImpl with the specified date time parser. + * + * @param dateTimeParser the date time parser + */ + public GeoRssItemImpl(DateTimeParser dateTimeParser) { + super(dateTimeParser); + } + + /** + * Returns the GeoRSS item data. + * + * @return the GeoRSS item data + */ + @Override + public GeoRssItemData getGeoRssItemData() { + return geoRssData; + } + + /** + * Compares this item with another object for equality. + * + * @param o the object to compare with + * @return true if both items are equal + */ + @Override + public boolean equals(Object o) { + if (!(o instanceof GeoRssItemImpl)) return false; + if (!super.equals(o)) return false; + GeoRssItemImpl that = (GeoRssItemImpl) o; + return Objects.equals(getGeoRssItemData(), that.getGeoRssItemData()); + } + + /** + * Returns the hash code for this item. + * + * @return the hash code + */ + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), getGeoRssItemData()); + } +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/MetaData.java b/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/MetaData.java new file mode 100644 index 00000000..bcad1ca6 --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/module/georss/internal/MetaData.java @@ -0,0 +1,290 @@ +package com.apptasticsoftware.rssreader.module.georss.internal; + +import com.apptasticsoftware.rssreader.module.georss.Coordinate; + +import java.util.*; + +/** + * Internal metadata class storing GeoRSS geographic information. + */ +public class MetaData { + private String geoRssPoint; + private String geoRssLine; + private String geoRssPolygon; + private String geoRssBox; + private Double geoRssElevation; + private Integer geoRssFloor; + private Double geoRssRadius; + private String geoRssFeatureTypeTag; + private String geoRssRelationshipTag; + private String geoRssFeatureName; + + /** + * Returns the GeoRSS point as an Optional string. + * + * @return the GeoRSS point + */ + public Optional getGeoRssPoint() { + return Optional.ofNullable(geoRssPoint); + } + + /** + * Returns the GeoRSS point as an Optional Coordinate. + * + * @return the coordinate + */ + public Optional getGeoRssPointAsCoordinate() { + return getGeoRssPoint().map(this::toCoordinate); + } + + /** + * Sets the GeoRSS point. + * + * @param geoRssPoint the point to set + */ + public void setGeoRssPoint(String geoRssPoint) { + this.geoRssPoint = geoRssPoint; + } + + /** + * Returns the GeoRSS line as an Optional string. + * + * @return the GeoRSS line + */ + public Optional getGeoRssLine() { + return Optional.ofNullable(geoRssLine); + } + + /** + * Returns the GeoRSS line as a list of Coordinates. + * + * @return the list of coordinates + */ + public List getGeoRssLineAsCoordinates() { + return getGeoRssLine().map(this::toCoordinates).orElse(Collections.emptyList()); + } + + /** + * Sets the GeoRSS line. + * + * @param geoRssLine the line to set + */ + public void setGeoRssLine(String geoRssLine) { + this.geoRssLine = geoRssLine; + } + + /** + * Returns the GeoRSS polygon as an Optional string. + * + * @return the GeoRSS polygon + */ + public Optional getGeoRssPolygon() { + return Optional.ofNullable(geoRssPolygon); + } + + /** + * Returns the GeoRSS polygon as a list of Coordinates. + * + * @return the list of coordinates + */ + public List getGeoRssPolygonAsCoordinates() { + return Optional.ofNullable(geoRssPolygon).map(this::toCoordinates).orElse(Collections.emptyList()); + } + + /** + * Sets the GeoRSS polygon. + * + * @param geoRssPolygon the polygon to set + */ + public void setGeoRssPolygon(String geoRssPolygon) { + this.geoRssPolygon = geoRssPolygon; + } + + /** + * Returns the GeoRSS box as an Optional string. + * + * @return the GeoRSS box + */ + public Optional getGeoRssBox() { + return Optional.ofNullable(geoRssBox); + } + + /** + * Returns the GeoRSS box as a list of Coordinates. + * + * @return the list of coordinates + */ + public List getGeoRssBoxAsCoordinates() { + return getGeoRssBox().map(this::toCoordinates).orElse(Collections.emptyList()); + } + + /** + * Sets the GeoRSS box. + * + * @param geoRssBox the box to set + */ + public void setGeoRssBox(String geoRssBox) { + this.geoRssBox = geoRssBox; + } + + /** + * Returns the GeoRSS elevation as an Optional Double. + * + * @return the elevation + */ + public Optional getGeoRssElevation() { + return Optional.ofNullable(geoRssElevation); + } + + /** + * Sets the GeoRSS elevation. + * + * @param geoRssElevation the elevation to set + */ + public void setGeoRssElevation(Double geoRssElevation) { + this.geoRssElevation = geoRssElevation; + } + + /** + * Returns the GeoRSS floor as an Optional Integer. + * + * @return the floor + */ + public Optional getGeoRssFloor() { + return Optional.ofNullable(geoRssFloor); + } + + /** + * Sets the GeoRSS floor. + * + * @param geoRssFloor the floor to set + */ + public void setGeoRssFloor(Integer geoRssFloor) { + this.geoRssFloor = geoRssFloor; + } + + /** + * Returns the GeoRSS radius as an Optional Double. + * + * @return the radius + */ + public Optional getGeoRssRadius() { + return Optional.ofNullable(geoRssRadius); + } + + /** + * Sets the GeoRSS radius. + * + * @param geoRssRadius the radius to set + */ + public void setGeoRssRadius(Double geoRssRadius) { + this.geoRssRadius = geoRssRadius; + } + + /** + * Returns the GeoRSS feature type tag as an Optional string. + * + * @return the feature type tag + */ + public Optional getGeoRssFeatureTypeTag() { + return Optional.ofNullable(geoRssFeatureTypeTag); + } + + /** + * Sets the GeoRSS feature type tag. + * + * @param geoRssFeatureTypeTag the feature type tag to set + */ + public void setGeoRssFeatureTypeTag(String geoRssFeatureTypeTag) { + this.geoRssFeatureTypeTag = geoRssFeatureTypeTag; + } + + /** + * Returns the GeoRSS relationship tag as an Optional string. + * + * @return the relationship tag + */ + public Optional getGeoRssRelationshipTag() { + return Optional.ofNullable(geoRssRelationshipTag); + } + + /** + * Sets the GeoRSS relationship tag. + * + * @param geoRssRelationshipTag the relationship tag to set + */ + public void setGeoRssRelationshipTag(String geoRssRelationshipTag) { + this.geoRssRelationshipTag = geoRssRelationshipTag; + } + + /** + * Returns the GeoRSS feature name as an Optional string. + * + * @return the feature name + */ + public Optional getGeoRssFeatureName() { + return Optional.ofNullable(geoRssFeatureName); + } + + /** + * Sets the GeoRSS feature name. + * + * @param geoRssFeatureName the feature name to set + */ + public void setGeoRssFeatureName(String geoRssFeatureName) { + this.geoRssFeatureName = geoRssFeatureName; + } + + /** + * Converts a space-separated coordinate string to a single Coordinate. + * + * @param value the coordinate string + * @return a Coordinate object + */ + private Coordinate toCoordinate(String value) { + var coordinates = toCoordinates(value); + return coordinates.isEmpty() ? null : coordinates.get(0); + + } + + /** + * Converts a space-separated coordinate string to a list of Coordinates. + * + * @param value the coordinate string + * @return a list of Coordinate objects + */ + private List toCoordinates(String value) { + List coordinates = new ArrayList<>(); + String[] parts = value.trim().split("\\s+"); + for (int i = 0; i < parts.length - 1; i += 2) { + Coordinate coordinate = new Coordinate(); + coordinate.setLatitude(Double.parseDouble(parts[i])); + coordinate.setLongitude(Double.parseDouble(parts[i + 1])); + coordinates.add(coordinate); + } + return coordinates; + } + + /** + * Compares this metadata with another object for equality. + * + * @param o the object to compare with + * @return true if all GeoRSS fields are equal + */ + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + MetaData that = (MetaData) o; + return Objects.equals(getGeoRssPoint(), that.getGeoRssPoint()) && Objects.equals(getGeoRssLine(), that.getGeoRssLine()) && Objects.equals(getGeoRssPolygon(), that.getGeoRssPolygon()) && Objects.equals(getGeoRssBox(), that.getGeoRssBox()) && Objects.equals(getGeoRssElevation(), that.getGeoRssElevation()) && Objects.equals(getGeoRssFloor(), that.getGeoRssFloor()) && Objects.equals(getGeoRssRadius(), that.getGeoRssRadius()) && Objects.equals(getGeoRssFeatureTypeTag(), that.getGeoRssFeatureTypeTag()) && Objects.equals(getGeoRssRelationshipTag(), that.getGeoRssRelationshipTag()) && Objects.equals(getGeoRssFeatureName(), that.getGeoRssFeatureName()); + } + + /** + * Returns the hash code for this metadata. + * + * @return the hash code + */ + @Override + public int hashCode() { + return Objects.hash(getGeoRssPoint(), getGeoRssLine(), getGeoRssPolygon(), getGeoRssBox(), getGeoRssElevation(), getGeoRssFloor(), getGeoRssRadius(), getGeoRssFeatureTypeTag(), getGeoRssRelationshipTag(), getGeoRssFeatureName()); + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 5580eeb0..d3ab2aed 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -32,6 +32,7 @@ exports com.apptasticsoftware.rssreader; exports com.apptasticsoftware.rssreader.util; + exports com.apptasticsoftware.rssreader.module.georss; exports com.apptasticsoftware.rssreader.module.itunes; exports com.apptasticsoftware.rssreader.module.mediarss; exports com.apptasticsoftware.rssreader.module.opensearch; diff --git a/src/test/java/com/apptasticsoftware/rssreader/module/georss/GeoRssFeedReaderTest.java b/src/test/java/com/apptasticsoftware/rssreader/module/georss/GeoRssFeedReaderTest.java new file mode 100644 index 00000000..a89ade9d --- /dev/null +++ b/src/test/java/com/apptasticsoftware/rssreader/module/georss/GeoRssFeedReaderTest.java @@ -0,0 +1,150 @@ +package com.apptasticsoftware.rssreader.module.georss; + +import com.apptasticsoftware.rssreader.module.georss.internal.*; +import com.apptasticsoftware.rssreader.util.Default; +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; + +import java.io.InputStream; +import java.util.List; +import java.util.stream.Collectors; + +import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresentAndIs; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@SuppressWarnings("java:S5961") +class GeoRssFeedReaderTest { + + @Test + void example1() { + var items = new GeoRssFeedReader().read(fromFile("georss/example1.xml")) + .collect(Collectors.toList()); + + assertEquals(1, items.size()); + var item = items.get(0); + var channel = (GeoRssChannel) item.getChannel(); + assertThat(channel.getTitle(), is("Earthquakes")); + assertThat(channel.getLink(), is("http://example.org/")); + assertThat(channel.getLastBuildDate(), isPresentAndIs("2005-12-13T18:30:02Z")); + assertThat(channel.getLastBuildDateAsZonedDateTime(), isPresentAndIs(Default.getDateTimeParser().parse("2005-12-13T18:30:02Z"))); + assertThat(channel.getManagingEditor(), isPresentAndIs("Dr. Thaddeus Remor")); + assertThat(channel.getGeoRssPoint(), isPresentAndIs("45.255 -71.91 1.2")); + assertThat(channel.getGeoRssPointAsCoordinate(), isPresentAndIs(new Coordinate(45.255, -71.91))); + assertThat(channel.getGeoRssLine(), isPresentAndIs("45.255 -110.44 46.45 -109.47 43.83 -109.85")); + assertThat(channel.getGeoRssLineAsCoordinates(), is(List.of(new Coordinate(45.255, -110.44), new Coordinate(46.45, -109.47), new Coordinate(43.83, -109.85)))); + assertThat(channel.getGeoRssPolygon(), isPresentAndIs("45.255 -110.44 46.45 -109.47 43.83 -109.85 45.255 -110.44")); + assertThat(channel.getGeoRssPolygonAsCoordinates(), is(List.of(new Coordinate(45.255, -110.44), new Coordinate(46.45, -109.47), new Coordinate(43.83, -109.85), new Coordinate(45.255, -110.44)))); + assertThat(channel.getGeoRssBox(), isPresentAndIs("42.942 -71.031 43.038 -69.855")); + assertThat(channel.getGeoRssBoxAsCoordinates(), is(List.of(new Coordinate(42.942, -71.031), new Coordinate(43.038, -69.855)))); + assertThat(channel.getGeoRssElevation(), isPresentAndIs(312.0)); + assertThat(channel.getGeoRssFloor(), isPresentAndIs(1)); + assertThat(channel.getGeoRssRadius(), isPresentAndIs(499.0)); + assertThat(channel.getGeoRssFeatureTypeTag(), isPresentAndIs("city-2")); + assertThat(channel.getGeoRssRelationshipTag(), isPresentAndIs("is-centered-at-2")); + assertThat(channel.getGeoRssFeatureName(), isPresentAndIs("Podunk-2")); + + assertThat(item.getTitle(), isPresentAndIs("M 3.2, Mona Passage")); + assertThat(item.getLink(), isPresentAndIs("http://example.org/2005/09/09/atom01")); + assertThat(item.getGuid(), isPresentAndIs("urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a")); + assertThat(item.getPubDate(), isPresentAndIs("2005-08-17T07:02:32Z")); + assertThat(item.getPubDateAsZonedDateTime(), isPresentAndIs(Default.getDateTimeParser().parse("2005-08-17T07:02:32Z"))); + assertThat(item.getUpdated(), isPresentAndIs("2005-08-17T07:02:32Z")); + assertThat(item.getUpdatedAsZonedDateTime(), isPresentAndIs(Default.getDateTimeParser().parse("2005-08-17T07:02:32Z"))); + assertThat(item.getGeoRssPoint(), isPresentAndIs("45.256 -71.92 1.2")); + assertThat(item.getGeoRssPointAsCoordinate(), isPresentAndIs(new Coordinate(45.256, -71.92))); + assertThat(item.getGeoRssLine(), isPresentAndIs("45.256 -110.45 46.46 -109.48 43.84 -109.86")); + assertThat(item.getGeoRssLineAsCoordinates(), is(List.of(new Coordinate(45.256, -110.45), new Coordinate(46.46, -109.48), new Coordinate(43.84, -109.86)))); + assertThat(item.getGeoRssPolygon(), isPresentAndIs("45.256 -110.45 46.46 -109.48 43.84 -109.86 45.256 -110.45")); + assertThat(item.getGeoRssPolygonAsCoordinates(), is(List.of(new Coordinate(45.256, -110.45), new Coordinate(46.46, -109.48), new Coordinate(43.84, -109.86), new Coordinate(45.256, -110.45)))); + assertThat(item.getGeoRssBox(), isPresentAndIs("42.943 -71.032 43.039 -69.856")); + assertThat(item.getGeoRssBoxAsCoordinates(), is(List.of(new Coordinate(42.943, -71.032), new Coordinate(43.039, -69.856)))); + assertThat(item.getGeoRssElevation(), isPresentAndIs(313.0)); + assertThat(item.getGeoRssFloor(), isPresentAndIs(2)); + assertThat(item.getGeoRssRadius(), isPresentAndIs(500.0)); + assertThat(item.getGeoRssFeatureTypeTag(), isPresentAndIs("city")); + assertThat(item.getGeoRssRelationshipTag(), isPresentAndIs("is-centered-at")); + assertThat(item.getGeoRssFeatureName(), isPresentAndIs("Podunk")); + } + + @Test + void example2() { + var items = new GeoRssFeedReader().read(fromFile("georss/example2.xml")) + .collect(Collectors.toList()); + + assertEquals(1, items.size()); + var item = items.get(0); + + var channel = (GeoRssChannel) item.getChannel(); + assertThat(channel.getTitle(), is("Earthquakes")); + assertThat(channel.getLink(), is("http://example.org/")); + assertThat(channel.getLastBuildDate(), isPresentAndIs("2005-12-13T18:30:02Z")); + assertThat(channel.getLastBuildDateAsZonedDateTime(), isPresentAndIs(Default.getDateTimeParser().parse("2005-12-13T18:30:02Z"))); + assertThat(channel.getManagingEditor(), isPresentAndIs("Dr. Thaddeus Remor")); + assertThat(channel.getGeoRssPoint(), isPresentAndIs("45.256 -71.92")); + assertThat(channel.getGeoRssPointAsCoordinate(), isPresentAndIs(new Coordinate(45.256, -71.92))); + assertThat(channel.getGeoRssLine(), isPresentAndIs("45.255 -110.44 46.45 -109.47 43.83 -109.85")); + assertThat(channel.getGeoRssLineAsCoordinates(), is(List.of(new Coordinate(45.255, -110.44), new Coordinate(46.45, -109.47), new Coordinate(43.83, -109.85)))); + assertThat(channel.getGeoRssPolygon(), isPresentAndIs("45.255 -110.44 46.45 -109.47 43.83 -109.85 45.255 -110.44")); + assertThat(channel.getGeoRssPolygonAsCoordinates(), is(List.of(new Coordinate(45.255, -110.44), new Coordinate(46.45, -109.47), new Coordinate(43.83, -109.85), new Coordinate(45.255, -110.44)))); + assertThat(channel.getGeoRssBox(), isPresentAndIs("42.942 -71.031 43.038 -69.855")); + assertThat(channel.getGeoRssBoxAsCoordinates(), is(List.of(new Coordinate(42.942, -71.031), new Coordinate(43.038, -69.855)))); + + assertThat(item.getTitle(), isPresentAndIs("M 3.2, Mona Passage")); + assertThat(item.getLink(), isPresentAndIs("http://example.org/2005/09/09/atom01")); + assertThat(item.getGuid(), isPresentAndIs("urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a")); + assertThat(item.getPubDate(), isPresentAndIs("2005-08-17T07:02:32Z")); + assertThat(item.getPubDateAsZonedDateTime(), isPresentAndIs(Default.getDateTimeParser().parse("2005-08-17T07:02:32Z"))); + assertThat(item.getUpdated(), isPresentAndIs("2005-08-17T07:02:32Z")); + assertThat(item.getUpdatedAsZonedDateTime(), isPresentAndIs(Default.getDateTimeParser().parse("2005-08-17T07:02:32Z"))); + assertThat(item.getGeoRssPoint(), isPresentAndIs("45.257 -71.93")); + assertThat(item.getGeoRssPointAsCoordinate(), isPresentAndIs(new Coordinate(45.257, -71.93))); + assertThat(item.getGeoRssLine(), isPresentAndIs("45.256 -110.45 46.46 -109.48 43.84 -109.86")); + assertThat(item.getGeoRssLineAsCoordinates(), is(List.of(new Coordinate(45.256, -110.45), new Coordinate(46.46, -109.48), new Coordinate(43.84, -109.86)))); + assertThat(item.getGeoRssPolygon(), isPresentAndIs("45.256 -110.45 46.46 -109.48 43.84 -109.86 45.256 -110.45")); + assertThat(item.getGeoRssPolygonAsCoordinates(), is(List.of(new Coordinate(45.256, -110.45), new Coordinate(46.46, -109.48), new Coordinate(43.84, -109.86), new Coordinate(45.256, -110.45)))); + assertThat(item.getGeoRssBox(), isPresentAndIs("42.943 -71.032 43.039 -69.856")); + assertThat(item.getGeoRssBoxAsCoordinates(), is(List.of(new Coordinate(42.943, -71.032), new Coordinate(43.039, -69.856)))); + } + + @Test + void example3() { + var items = new GeoRssFeedReader().read(fromFile("georss/example3.xml")) + .collect(Collectors.toList()); + + assertEquals(1, items.size()); + var item = items.get(0); + + var channel = (GeoRssChannel) item.getChannel(); + assertThat(channel.getTitle(), is("USGS M5+ Earthquakes")); + assertThat(channel.getDescription(), is("Real-time, worldwide earthquake list for the past 7 days")); + assertThat(channel.getLink(), is("https://earthquake.usgs.gov/eqcenter/")); + assertThat(channel.getPubDate(), isPresentAndIs("Thu, 27 Dec 2007 23:56:15 PST")); + assertThat(channel.getPubDateAsZonedDateTime(), isPresentAndIs(Default.getDateTimeParser().parse("Thu, 27 Dec 2007 23:56:15 PST"))); + assertThat(channel.getGeoRssPoint(), isPresentAndIs("5.5318 95.8971")); + assertThat(channel.getGeoRssPointAsCoordinate(), isPresentAndIs(new Coordinate(5.5318, 95.8971))); + + assertThat(item.getPubDate(), isPresentAndIs("Fri, 28 Dec 2007 05:24:17 GMT")); + assertThat(item.getPubDateAsZonedDateTime(), isPresentAndIs(Default.getDateTimeParser().parse("Fri, 28 Dec 2007 05:24:17 GMT"))); + assertThat(item.getTitle(), isPresentAndIs("M 5.3, northern Sumatra, Indonesia")); + assertThat(item.getDescription(), isPresentAndIs("December 28, 2007 05:24:17 GMT")); + assertThat(item.getLink(), isPresentAndIs("https://earthquake.usgs.gov/eqcenter/recenteqsww/Quakes/us2007llai.php")); + assertThat(item.getGeoRssPoint(), isPresentAndIs("5.5319 95.8972")); + assertThat(item.getGeoRssPointAsCoordinate(), isPresentAndIs(new Coordinate(5.5319, 95.8972))); + } + + @Test + void equalsContract() { + EqualsVerifier.simple().forClass(GeoRssChannelImpl.class).withNonnullFields("geoRssData").withIgnoredFields("dateTimeParser").withIgnoredFields("category").withNonnullFields("categories").withIgnoredFields("syUpdatePeriod").withIgnoredFields("syUpdateFrequency").verify(); + EqualsVerifier.simple().forClass(GeoRssChannelDataImpl.class).verify(); + EqualsVerifier.simple().forClass(GeoRssItemImpl.class).withNonnullFields("geoRssData").withIgnoredFields("defaultComparator").withIgnoredFields("dateTimeParser").withIgnoredFields("category").withNonnullFields("categories").withIgnoredFields("enclosure").withNonnullFields("enclosures").verify(); + EqualsVerifier.simple().forClass(GeoRssItemDataImpl.class).verify(); + EqualsVerifier.simple().forClass(MetaData.class).verify(); + EqualsVerifier.simple().forClass(Coordinate.class).verify(); + } + + private InputStream fromFile(String fileName) { + return getClass().getClassLoader().getResourceAsStream(fileName); + } +} diff --git a/src/test/resources/georss/example1.xml b/src/test/resources/georss/example1.xml new file mode 100644 index 00000000..ed401837 --- /dev/null +++ b/src/test/resources/georss/example1.xml @@ -0,0 +1,42 @@ + + + Earthquakes + International earthquake observation labs + + 2005-12-13T18:30:02Z + + Dr. Thaddeus Remor + tremor@quakelab.edu + + urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 + 45.255 -71.91 1.2 + 45.255 -110.44 46.45 -109.47 43.83 -109.85 + 45.255 -110.44 46.45 -109.47 43.83 -109.85 45.255 -110.44 + 42.942 -71.031 43.038 -69.855 + 312 + 1 + 499 + city-2 + is-centered-at-2 + Podunk-2 + + + M 3.2, Mona Passage + + urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a + 2005-08-17T07:02:32Z + We just had a big one. + 45.256 -71.92 1.2 + 45.256 -110.45 46.46 -109.48 43.84 -109.86 + 45.256 -110.45 46.46 -109.48 43.84 -109.86 45.256 -110.45 + 42.943 -71.032 43.039 -69.856 + 313 + 2 + 500 + city + is-centered-at + Podunk + + diff --git a/src/test/resources/georss/example2.xml b/src/test/resources/georss/example2.xml new file mode 100644 index 00000000..8ec65a53 --- /dev/null +++ b/src/test/resources/georss/example2.xml @@ -0,0 +1,80 @@ + + + Earthquakes + International earthquake observation labs + + 2005-12-13T18:30:02Z + + Dr. Thaddeus Remor + tremor@quakelab.edu + + urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 + + + 45.256 -71.92 + + + + + + 45.255 -110.44 46.45 -109.47 43.83 -109.85 + + + + + + + + + 45.255 -110.44 46.45 -109.47 43.83 -109.85 45.255 -110.44 + + + + + + + + 42.942 -71.031 + 43.038 -69.855 + + + + + M 3.2, Mona Passage + + urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a + 2005-08-17T07:02:32Z + We just had a big one. + + + 45.257 -71.93 + + + + + + 45.256 -110.45 46.46 -109.48 43.84 -109.86 + + + + + + + + + 45.256 -110.45 46.46 -109.48 43.84 -109.86 45.256 -110.45 + + + + + + + + 42.943 -71.032 + 43.039 -69.856 + + + + diff --git a/src/test/resources/georss/example3.xml b/src/test/resources/georss/example3.xml new file mode 100644 index 00000000..d06442b7 --- /dev/null +++ b/src/test/resources/georss/example3.xml @@ -0,0 +1,24 @@ + + + + + USGS M5+ Earthquakes + Real-time, worldwide earthquake list for the past 7 days + https://earthquake.usgs.gov/eqcenter/ + U.S. Geological Survey + Thu, 27 Dec 2007 23:56:15 PST + 95.8971 + 5.5318 + + Fri, 28 Dec 2007 05:24:17 GMT + M 5.3, northern Sumatra, Indonesia + December 28, 2007 05:24:17 GMT + https://earthquake.usgs.gov/eqcenter/recenteqsww/Quakes/us2007llai.php + 5.5319 + 95.8972 + + + \ No newline at end of file