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 &lt;channel> 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 &lt;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 examples), and the
036 * link and title may be omitted. All elements of an item are optional, however
037 * at least one of title or description must be present.
038 * </p>
039 * 
040 * @author Bill Brown
041 * 
042 */
043public class Channel implements Serializable {
044
045        /**
046         * 
047         */
048        private static final long serialVersionUID = -6193654133883709775L;
049
050        /* required fields */
051        private final Title title;
052
053        private final Link link;
054
055        private final Description description;
056
057        /* optional fields */
058        private final Language language;
059
060        private final Copyright copyright;
061
062        private final ManagingEditor managingEditor;
063
064        private final WebMaster webMaster;
065
066        private final PubDate pubDate;
067
068        private final LastBuildDate lastBuildDate;
069
070        private final List<Category> categories;
071
072        private final Generator generator;
073
074        private final Docs docs;
075
076        private final Cloud cloud;
077
078        private final TTL ttl;
079
080        private final Image image;
081
082        private final Rating rating;
083
084        private final TextInput textInput;
085
086        private final SkipHours skipHours;
087
088        private final SkipDays skipDays;
089
090        private final List<Item> items;
091
092        private final List<Extension> extensions;
093
094        private List<String> unboundPrefixes;
095
096        Channel(Title title, Link link, Description description, Language language,
097                        Copyright copyright, ManagingEditor managingEditor,
098                        WebMaster webMaster, PubDate pubDate, LastBuildDate lastBuildDate,
099                        List<Category> categories, Generator generator, Docs docs,
100                        Cloud cloud, TTL ttl, Image image, Rating rating,
101                        TextInput textInput, SkipHours skipHours, SkipDays skipDays,
102                        List<Extension> extensions, List<Item> items)
103                        throws RSSpectException {
104
105                // make sure title is present
106                if (title == null) {
107                        throw new RSSpectException(
108                                        "channel elements MUST contain a title element.");
109                }
110                this.title = new Title(title);
111
112                // make sure link is present
113                if (link == null) {
114                        throw new RSSpectException(
115                                        "channel elements MUST contain a link element.");
116                }
117                this.link = new Link(link);
118
119                // make sure description is present
120                if (description == null) {
121                        throw new RSSpectException(
122                                        "channel elements MUST contain a description element.");
123                }
124                this.description = new Description(description);
125
126                this.language = (language == null) ? null : new Language(language);
127                this.copyright = (copyright == null) ? null : new Copyright(copyright);
128                this.managingEditor = (managingEditor == null) ? null
129                                : new ManagingEditor(managingEditor);
130                this.webMaster = (webMaster == null) ? null : new WebMaster(webMaster);
131                this.pubDate = (pubDate == null) ? null : new PubDate(pubDate
132                                .getDateTime());
133                this.lastBuildDate = (lastBuildDate == null) ? null
134                                : new LastBuildDate(lastBuildDate.getDateTime());
135                if (categories == null) {
136                        this.categories = null;
137                } else {
138                        this.categories = new LinkedList<Category>();
139                        for (Category category : categories) {
140                                this.categories.add(new Category(category));
141                        }
142                }
143                this.generator = (generator == null) ? null : new Generator(generator
144                                .getGenerator());
145                this.docs = (docs == null) ? null : new Docs(docs.getDocs());
146                this.cloud = (cloud == null) ? null : new Cloud(cloud);
147                this.ttl = (ttl == null) ? null : new TTL(ttl.getTtl());
148                this.image = (image == null) ? null : new Image(image);
149                this.rating = (rating == null) ? null : new Rating(rating.getRating());
150                this.textInput = (textInput == null) ? null : new TextInput(textInput);
151                this.skipHours = (skipHours == null) ? null : new SkipHours(skipHours);
152                this.skipDays = (skipDays == null) ? null : new SkipDays(skipDays);
153
154                // check that the extension prefixes are bound to a namespace
155                this.unboundPrefixes = new LinkedList<String>();
156
157                if (items == null) {
158                        this.items = null;
159                } else {
160                        this.items = new LinkedList<Item>();
161                        for (Item item : items) {
162                                // add any unbound prefixes to test.
163                                if (item.getUnboundPrefixes() != null) {
164                                        this.unboundPrefixes.addAll(item.getUnboundPrefixes());
165                                }
166                                this.items.add(new Item(item));
167                        }
168                }
169
170                if (extensions == null) {
171                        this.extensions = null;
172                } else {
173                        this.extensions = new LinkedList<Extension>();
174
175                        for (Extension extension : extensions) {
176                                // check that the extension prefix is bound to a namespace
177                                String namespacePrefix = extension.getNamespacePrefix();
178                                if (namespacePrefix != null) {
179                                        this.unboundPrefixes.add(namespacePrefix);
180                                }
181                                this.extensions.add(new Extension(extension));
182                        }
183                }
184
185                this.unboundPrefixes = (this.unboundPrefixes.size() == 0) ? null
186                                : this.unboundPrefixes;
187        }
188
189        Channel(Channel channel) {
190                this.title = channel.getTitle();
191                this.link = channel.getLink();
192                this.description = channel.getDescription();
193                this.language = channel.getLanguage();
194                this.copyright = channel.getCopyright();
195                this.managingEditor = channel.getManagingEditor();
196                this.webMaster = channel.getWebMaster();
197                this.pubDate = channel.getPubDate();
198                this.lastBuildDate = channel.getLastBuildDate();
199                this.categories = channel.getCategories();
200                this.generator = channel.getGenerator();
201                this.docs = channel.getDocs();
202                this.cloud = channel.getCloud();
203                this.ttl = channel.getTtl();
204                this.image = channel.getImage();
205                this.rating = channel.getRating();
206                this.textInput = channel.getTextInput();
207                this.skipHours = channel.getSkipHours();
208                this.skipDays = channel.getSkipDays();
209                this.items = channel.getItems();
210                this.extensions = channel.getExtensions();
211                this.unboundPrefixes = channel.getUnboundPrefixes();
212        }
213
214        /**
215         * @return the title.
216         */
217        public Title getTitle() {
218                return new Title(title);
219        }
220
221        /**
222         * @return the link element.
223         */
224        public Link getLink() {
225                return new Link(link);
226        }
227
228        /**
229         * @return the description element.
230         */
231        public Description getDescription() {
232                return new Description(description.getDescription());
233        }
234
235        /**
236         * @return the language element.
237         */
238        public Language getLanguage() {
239                return (language == null) ? null : new Language(language);
240        }
241
242        /**
243         * @return the copyright element
244         */
245        public Copyright getCopyright() {
246                return (copyright == null) ? null : new Copyright(copyright);
247        }
248
249        /**
250         * @return the managing editor element.
251         */
252        public ManagingEditor getManagingEditor() {
253                return (managingEditor == null) ? null : new ManagingEditor(
254                                managingEditor);
255        }
256
257        /**
258         * @return the webmaster element.
259         */
260        public WebMaster getWebMaster() {
261                return (webMaster == null) ? null : new WebMaster(webMaster);
262        }
263
264        /**
265         * @return the pubDate element.
266         */
267        public PubDate getPubDate() {
268                return (pubDate == null) ? null : new PubDate(pubDate.getDateTime());
269        }
270
271        /**
272         * @return the last build date element.
273         */
274        public LastBuildDate getLastBuildDate() {
275                return (lastBuildDate == null) ? null : new LastBuildDate(lastBuildDate
276                                .getDateTime());
277        }
278
279        /**
280         * @return the list of categories.
281         */
282        public List<Category> getCategories() {
283                if (categories == null) {
284                        return null;
285                } else {
286                        List<Category> catsCopy = new LinkedList<Category>();
287                        for (Category category : this.categories) {
288                                catsCopy.add(new Category(category));
289                        }
290                        return catsCopy;
291                }
292        }
293
294        /**
295         * @return the generator element.
296         */
297        public Generator getGenerator() {
298                return (generator == null) ? null : new Generator(generator);
299        }
300
301        /**
302         * @return the docs element.
303         */
304        public Docs getDocs() {
305                return (docs == null) ? null : new Docs(docs);
306        }
307
308        /**
309         * @return the cloud element.
310         */
311        public Cloud getCloud() {
312                return (cloud == null) ? null : new Cloud(cloud);
313        }
314
315        /**
316         * @return the ttl element.
317         */
318        public TTL getTtl() {
319                return (ttl == null) ? null : new TTL(ttl);
320        }
321
322        /**
323         * @return the image element.
324         */
325        public Image getImage() {
326                return (image == null) ? null : new Image(image);
327        }
328
329        /**
330         * @return the rating element.
331         */
332        public Rating getRating() {
333                return (rating == null) ? null : new Rating(rating);
334        }
335
336        /**
337         * @return the textInput element.
338         */
339        public TextInput getTextInput() {
340                return (textInput == null) ? null : new TextInput(textInput);
341        }
342
343        /**
344         * @return the skipHours element.
345         */
346        public SkipHours getSkipHours() {
347                return (skipHours == null) ? null : new SkipHours(skipHours);
348        }
349
350        /**
351         * @return the skipDays element.
352         */
353        public SkipDays getSkipDays() {
354                return (skipDays == null) ? null : new SkipDays(skipDays);
355        }
356
357        /**
358         * @return the list of items.
359         */
360        public List<Item> getItems() {
361                if (items == null) {
362                        return null;
363                } else {
364                        List<Item> itemsCopy = new LinkedList<Item>();
365                        for (Item item : this.items) {
366                                itemsCopy.add(new Item(item));
367                        }
368                        return itemsCopy;
369                }
370        }
371
372        /**
373         * 
374         * @return the extensions for this entry.
375         */
376        public List<Extension> getExtensions() {
377                if (extensions == null) {
378                        return null;
379                }
380                List<Extension> extsCopy = new LinkedList<Extension>();
381                for (Extension extension : this.extensions) {
382                        extsCopy.add(new Extension(extension));
383                }
384                return extsCopy;
385        }
386
387        /**
388         * @param catValue
389         *            the value of the category.
390         * @return the category name matching this item or null if not found.
391         */
392        public Category getCategory(String catValue) {
393                if (this.categories != null) {
394                        for (Category category : this.categories) {
395                                if (category.getCategory() != null
396                                                && category.getCategory().equals(catValue)) {
397                                        return new Category(category);
398                                }
399                        }
400                }
401                return null;
402        }
403
404        /**
405         * @param titleOrDescription
406         *            the title or description data.
407         * @return the item with this title or description. returns null if not
408         *         found.
409         */
410        public Item getItem(String titleOrDescription) {
411                if (this.items != null) {
412                        for (Item item : this.items) {
413                                if ((item.getTitle() != null
414                                                && item.getTitle().getTitle() != null && item
415                                                .getTitle().getTitle().equals(titleOrDescription))
416                                                || (item.getDescription() != null && item
417                                                                .getDescription().getDescription().equals(
418                                                                                titleOrDescription))) {
419                                        return new Item(item);
420                                }
421                        }
422                }
423                return null;
424        }
425
426        /**
427         * @param extName
428         *            the element name of the extension. eg. "atom:link" or
429         *            "someExtension"
430         * @return the extension matching the element or null if not found.
431         */
432        public Extension getExtension(String extName) {
433                if (this.extensions != null) {
434                        for (Extension extension : this.extensions) {
435                                if (extension.getElementName().equals(extName)) {
436                                        return new Extension(extension);
437                                }
438                        }
439                }
440                return null;
441        }
442
443        /**
444         * Shows the contents of the &lt;channel> element.
445         */
446        @Override
447        public String toString() {
448                StringBuilder sb = new StringBuilder("<channel>");
449
450                sb.append(title);
451
452                sb.append(link);
453
454                sb.append(description);
455
456                if (language != null) {
457                        sb.append(language);
458                }
459
460                if (copyright != null) {
461                        sb.append(copyright);
462                }
463
464                if (managingEditor != null) {
465                        sb.append(managingEditor);
466                }
467
468                if (webMaster != null) {
469                        sb.append(webMaster);
470                }
471
472                if (pubDate != null) {
473                        sb.append(pubDate);
474                }
475
476                if (lastBuildDate != null) {
477                        sb.append(lastBuildDate);
478                }
479
480                if (categories != null) {
481                        for (Category category : categories) {
482                                sb.append(category);
483                        }
484                }
485
486                if (generator != null) {
487                        sb.append(generator);
488                }
489
490                if (docs != null) {
491                        sb.append(docs);
492                }
493
494                if (cloud != null) {
495                        sb.append(cloud);
496                }
497
498                if (ttl != null) {
499                        sb.append(ttl);
500                }
501
502                if (image != null) {
503                        sb.append(image);
504                }
505
506                if (rating != null) {
507                        sb.append(rating);
508                }
509
510                if (textInput != null) {
511                        sb.append(textInput);
512                }
513
514                if (skipHours != null) {
515                        sb.append(skipHours);
516                }
517
518                if (skipDays != null) {
519                        sb.append(skipDays);
520                }
521
522                if (items != null) {
523                        for (Item item : items) {
524                                sb.append(item);
525                        }
526                }
527
528                if (extensions != null) {
529                        for (Extension extension : extensions) {
530                                sb.append(extension);
531                        }
532                }
533
534                sb.append("</channel>");
535                return sb.toString();
536        }
537
538        List<String> getUnboundPrefixes() {
539                return unboundPrefixes;
540        }
541        
542        @Override
543        public boolean equals(Object obj) {
544                if (obj == this) {
545                        return true;
546                }
547                if (!(obj instanceof Channel)) {
548                        return false;
549                }
550                return this.toString().equals(obj.toString());
551        }
552        
553        @Override public int hashCode() {
554                return toString().hashCode();
555        }
556}