001/** 002 * Copyright 2011 Bill Brown 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.colorfulsoftware.rss; 017 018import java.io.Serializable; 019import java.util.LinkedList; 020import java.util.List; 021 022/** 023 * <p> 024 * The <item> element. 025 * </p> 026 * <p> 027 * From the <a href="http://cyber.law.harvard.edu/rss/rss.html">RSS 2.0 028 * specification</a>... 029 * </p> 030 * <p> 031 * A channel may contain any number of <item>s. An item may represent a 032 * "story" -- much like a story in a newspaper or magazine; if so its 033 * description is a synopsis of the story, and the link points to the full 034 * story. An item may also be complete in itself, if so, the description 035 * contains the text (entity-encoded HTML is allowed; see <a 036 * href="http://cyber.law.harvard.edu/rss/encodingDescriptions.html" 037 * >examples</a>), and the link and title may be omitted. All elements of an 038 * item are optional, however at least one of title or description must be 039 * present. 040 * </p> 041 * 042 * @author Bill Brown 043 * 044 */ 045public class Item implements Serializable { 046 047 /** 048 * 049 */ 050 private static final long serialVersionUID = 6856122030764773780L; 051 /* one of the two of these is required */ 052 private final Title title; 053 054 private final Description description; 055 056 /* optional elements */ 057 private final Link link; 058 059 private final Author author; 060 061 private final List<Category> categories; 062 063 private final Comments comments; 064 065 private final Enclosure enclosure; 066 067 private final GUID guid; 068 069 private final PubDate pubDate; 070 071 private final Source source; 072 073 private final List<Extension> extensions; 074 075 private List<String> unboundPrefixes; 076 077 Item(Title title, Link link, Description description, Author author, 078 List<Category> categories, Comments comments, Enclosure enclosure, 079 GUID guid, PubDate pubDate, Source source, 080 List<Extension> extensions) throws RSSpectException { 081 082 // make sure title or description is present 083 if (title == null && description == null) { 084 throw new RSSpectException( 085 "item elements MUST contain either a title or description element."); 086 } 087 this.title = (title == null) ? null : new Title(title.getTitle()); 088 089 this.link = (link == null) ? null : new Link(link); 090 091 this.description = (description == null) ? null : new Description( 092 description.getDescription()); 093 094 this.author = (author == null) ? null : new Author(author.getAuthor()); 095 096 if (categories == null) { 097 this.categories = null; 098 } else { 099 this.categories = new LinkedList<Category>(); 100 for (Category category : categories) { 101 this.categories.add(new Category(category)); 102 } 103 } 104 105 this.comments = (comments == null) ? null : new Comments(comments 106 .getComments()); 107 108 this.enclosure = (enclosure == null) ? null : new Enclosure(enclosure); 109 110 this.guid = (guid == null) ? null : new GUID(guid.getIsPermaLink(), 111 guid.getGuid()); 112 113 this.pubDate = (pubDate == null) ? null : new PubDate(pubDate 114 .getDateTime()); 115 116 this.source = (source == null) ? null : new Source(source); 117 118 // check that the extension prefixes are bound to a namespace 119 this.unboundPrefixes = new LinkedList<String>(); 120 121 if (extensions == null) { 122 this.extensions = null; 123 } else { 124 this.extensions = new LinkedList<Extension>(); 125 126 for (Extension extension : extensions) { 127 // check that the extension prefix is bound to a namespace 128 String namespacePrefix = extension.getNamespacePrefix(); 129 if (namespacePrefix != null) { 130 this.unboundPrefixes.add(namespacePrefix); 131 } 132 this.extensions.add(new Extension(extension)); 133 } 134 } 135 136 this.unboundPrefixes = (this.unboundPrefixes.size() == 0) ? null 137 : this.unboundPrefixes; 138 } 139 140 Item(Item item) { 141 this.title = item.getTitle(); 142 this.description = item.getDescription(); 143 this.link = item.getLink(); 144 this.author = item.getAuthor(); 145 this.categories = item.getCategories(); 146 this.comments = item.getComments(); 147 this.enclosure = item.getEnclosure(); 148 this.guid = item.getGuid(); 149 this.pubDate = item.getPubDate(); 150 this.source = item.getSource(); 151 this.extensions = item.getExtensions(); 152 } 153 154 /** 155 * @return the title object. 156 */ 157 public Title getTitle() { 158 return (title == null) ? null : new Title(title); 159 } 160 161 /** 162 * @return the description object. 163 */ 164 public Description getDescription() { 165 return (description == null) ? null : new Description(description 166 .getDescription()); 167 } 168 169 /** 170 * @return the link object. 171 */ 172 public Link getLink() { 173 return (link == null) ? null : new Link(link); 174 } 175 176 /** 177 * @return the author object. 178 */ 179 public Author getAuthor() { 180 return (author == null) ? null : new Author(author); 181 } 182 183 /** 184 * @return the list of categories. 185 */ 186 public List<Category> getCategories() { 187 if (categories == null) { 188 return null; 189 } else { 190 List<Category> catsCopy = new LinkedList<Category>(); 191 for (Category category : this.categories) { 192 catsCopy.add(new Category(category)); 193 } 194 return catsCopy; 195 } 196 } 197 198 /** 199 * @return the comments object. 200 */ 201 public Comments getComments() { 202 return (comments == null) ? null : new Comments(comments); 203 } 204 205 /** 206 * @return the enclosure object. 207 */ 208 public Enclosure getEnclosure() { 209 return (enclosure == null) ? null : new Enclosure(enclosure); 210 } 211 212 /** 213 * @return the guid object. 214 */ 215 public GUID getGuid() { 216 return (guid == null) ? null : new GUID(guid); 217 } 218 219 /** 220 * @return the published date object. 221 */ 222 public PubDate getPubDate() { 223 return (pubDate == null) ? null : new PubDate(pubDate.getDateTime()); 224 } 225 226 /** 227 * @return the source object. 228 */ 229 public Source getSource() { 230 return (source == null) ? null : new Source(source); 231 } 232 233 /** 234 * 235 * @return the list of extensions for this entry. 236 */ 237 public List<Extension> getExtensions() { 238 if (extensions == null) { 239 return null; 240 } 241 List<Extension> extsCopy = new LinkedList<Extension>(); 242 for (Extension extension : this.extensions) { 243 extsCopy.add(new Extension(extension)); 244 } 245 return extsCopy; 246 } 247 248 /** 249 * @param catValue 250 * the value for the category. 251 * @return the category object if found otherwise null. 252 */ 253 public Category getCategory(String catValue) { 254 if (this.categories != null) { 255 for (Category category : this.categories) { 256 if (category.getCategory() != null 257 && category.getCategory().equals(catValue)) { 258 return new Category(category); 259 } 260 } 261 } 262 return null; 263 } 264 265 /** 266 * @param extName 267 * the element name of the extension. eg. "atom:link" or 268 * "someExtension" 269 * @return the extension matching the element or null if not found. 270 */ 271 public Extension getExtension(String extName) { 272 if (this.extensions != null) { 273 for (Extension extension : this.extensions) { 274 if (extension.getElementName().equals(extName)) { 275 return new Extension(extension); 276 } 277 } 278 } 279 return null; 280 } 281 282 /** 283 * Shows the contents of the <item> element. 284 */ 285 @Override 286 public String toString() { 287 StringBuilder sb = new StringBuilder("<item>"); 288 289 if (title != null) { 290 sb.append(title); 291 } 292 293 if (description != null) { 294 sb.append(description); 295 } 296 297 if (link != null) { 298 sb.append(link); 299 } 300 301 if (author != null) { 302 sb.append(author); 303 } 304 305 if (categories != null) { 306 for (Category category : categories) { 307 sb.append(category); 308 } 309 } 310 311 if (comments != null) { 312 sb.append(comments); 313 } 314 315 if (enclosure != null) { 316 sb.append(enclosure); 317 } 318 319 if (guid != null) { 320 sb.append(guid); 321 } 322 323 if (pubDate != null) { 324 sb.append(pubDate); 325 } 326 327 if (source != null) { 328 sb.append(source); 329 } 330 331 if (extensions != null) { 332 for (Extension extension : extensions) { 333 sb.append(extension); 334 } 335 } 336 337 sb.append("</item>"); 338 return sb.toString(); 339 } 340 341 List<String> getUnboundPrefixes() { 342 return unboundPrefixes; 343 } 344 345 @Override 346 public boolean equals(Object obj) { 347 if (obj == this) { 348 return true; 349 } 350 if (!(obj instanceof Item)) { 351 return false; 352 } 353 return this.toString().equals(obj.toString()); 354 } 355 356 @Override public int hashCode() { 357 return toString().hashCode(); 358 } 359}