diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8c2b731
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+# netbeans specific
+nbproject/private/
+nbproject/configs/
+build/
+nbbuild/
+dist/
+nbdist/
+
+_notes/
+playground/
+tmp/
+temp/
+*~
+*.bak
+*.old
+*.orig
+*.rej
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..4d7e56c
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,53 @@
+
+ Builds, tests, and runs the project BlogReader.
+
+
+
diff --git a/license.txt b/license.txt
new file mode 100644
index 0000000..81d13f0
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,20 @@
+Copyright (c) 2013 Kristof Neirynck
+http://crydust.be/
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/manifest.mf b/manifest.mf
new file mode 100644
index 0000000..328e8e5
--- /dev/null
+++ b/manifest.mf
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+
diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml
new file mode 100644
index 0000000..2ad143c
--- /dev/null
+++ b/nbproject/build-impl.xml
@@ -0,0 +1,1445 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set platform.home
+ Must set platform.bootcp
+ Must set platform.java
+ Must set platform.javac
+
+ The J2SE Platform is not correctly set up.
+ Your active platform is: ${platform.active}, but the corresponding property "platforms.${platform.active}.home" is not found in the project's properties files.
+ Either open the project in the IDE and setup the Platform with the same name or add it manually.
+ For example like this:
+ ant -Duser.properties.file=<path_to_property_file> jar (where you put the property "platforms.${platform.active}.home" in a .properties file)
+ or ant -Dplatforms.${platform.active}.home=<path_to_JDK_home> jar (where no properties file is used)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set src.dir
+ Must set test.src.dir
+ Must set build.dir
+ Must set dist.dir
+ Must set build.classes.dir
+ Must set dist.javadoc.dir
+ Must set build.test.classes.dir
+ Must set build.test.results.dir
+ Must set build.classes.excludes
+ Must set dist.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No tests executed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set JVM to use for profiling in profiler.info.jvm
+ Must set profiler agent JVM arguments in profiler.info.jvmargs.agent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+
+
+
+
+
+ ${platform.java} -cp "${run.classpath.with.dist.jar}" ${main.class}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+ ${platform.java} -jar "${dist.jar.resolved}"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must set fix.includes
+
+
+
+
+
+
+
+
+
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set profile.class
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+
+
+
+
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+
+ Must select some files in the IDE or set test.includes
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set test.includes
+
+
+
+ Some tests failed; see details above.
+
+
+
+ Must select some files in the IDE or set test.class
+ Must select some method in the IDE or set test.method
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+ Must select one file in the IDE or set test.class
+
+
+
+ Must select one file in the IDE or set test.class
+ Must select some method in the IDE or set test.method
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties
new file mode 100644
index 0000000..ac4ca97
--- /dev/null
+++ b/nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=8e0adbf5
+build.xml.script.CRC32=130f1d84
+build.xml.stylesheet.CRC32=28e38971@1.55.1.46
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=8e0adbf5
+nbproject/build-impl.xml.script.CRC32=9366d7d7
+nbproject/build-impl.xml.stylesheet.CRC32=c6d2a60f@1.55.1.46
diff --git a/nbproject/jfx-impl.xml b/nbproject/jfx-impl.xml
new file mode 100644
index 0000000..699e1db
--- /dev/null
+++ b/nbproject/jfx-impl.xml
@@ -0,0 +1,3182 @@
+
+
+
+
+ JavaFX-specific Ant calls
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${cssfileslist}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/nbproject/project.properties b/nbproject/project.properties
new file mode 100644
index 0000000..ff10896
--- /dev/null
+++ b/nbproject/project.properties
@@ -0,0 +1,125 @@
+annotation.processing.enabled=true
+annotation.processing.enabled.in.editor=false
+annotation.processing.processors.list=
+annotation.processing.run.all.processors=true
+annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
+application.title=BlogReader
+application.vendor=kristof
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+build.generated.sources.dir=${build.dir}/generated-sources
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+compile.on.save=true
+compile.on.save.unsupported.javafx=true
+# Uncomment to specify the preferred debugger connection transport:
+#debug.transport=dt_socket
+debug.classpath=\
+ ${run.classpath}
+debug.test.classpath=\
+ ${run.test.classpath}
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/BlogReader.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+endorsed.classpath=
+excludes=
+includes=**
+# Non-JavaFX jar file creation is deactivated in JavaFX 2.0+ projects
+jar.archive.disabled=true
+jar.compress=false
+jar.index=${jnlp.enabled}
+javac.classpath=\
+ ${javafx.runtime}/lib/jfxrt.jar:\
+ ${javafx.runtime}/lib/deploy.jar:\
+ ${javafx.runtime}/lib/javaws.jar:\
+ ${javafx.runtime}/lib/plugin.jar
+# Space-separated list of extra javac options
+javac.compilerargs=
+javac.deprecation=false
+javac.processorpath=\
+ ${javac.classpath}
+javac.source=1.7
+javac.target=1.7
+javac.test.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+javac.test.processorpath=\
+ ${javac.test.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=
+javafx.binarycss=false
+javafx.deploy.adddesktopshortcut=false
+javafx.deploy.addstartmenushortcut=false
+javafx.deploy.allowoffline=true
+# If true, application update mode is set to 'background', if false, update mode is set to 'eager'
+javafx.deploy.backgroundupdate=false
+javafx.deploy.embedJNLP=true
+javafx.deploy.includeDT=true
+javafx.deploy.installpermanently=false
+javafx.deploy.permissionselevated=true
+# Set true to prevent creation of temporary copy of deployment artifacts before each run (disables concurrent runs)
+javafx.disable.concurrent.runs=false
+# Set true to enable multiple concurrent runs of the same WebStart or Run-in-Browser project
+javafx.enable.concurrent.external.runs=false
+# This is a JavaFX project
+javafx.enabled=true
+javafx.fallback.class=com.javafx.main.NoJavaFXFallback
+# Main class for JavaFX
+javafx.main.class=blogreader.BlogReader
+javafx.native.bundling.enabled=false
+javafx.native.bundling.type=none
+javafx.preloader.class=
+# This project does not use Preloader
+javafx.preloader.enabled=false
+javafx.preloader.jar.filename=
+javafx.preloader.jar.path=
+javafx.preloader.project.path=
+javafx.preloader.type=none
+# Set true for GlassFish only. Rebases manifest classpaths of JARs in lib dir. Not usable with signed JARs.
+javafx.rebase.libs=false
+javafx.run.height=600
+javafx.run.width=800
+javafx.runtime=${platforms.JDK_1.7.javafx.runtime.home}
+javafx.sdk=${platforms.JDK_1.7.javafx.sdk.home}
+javafx.signing.enabled=true
+javafx.signing.type=self
+jnlp.codebase.type=no.codebase
+jnlp.descriptor=application
+# Pre-JavaFX 2.0 WebStart is deactivated in JavaFX 2.0+ projects
+jnlp.enabled=false
+jnlp.mixed.code=default
+jnlp.offline-allowed=false
+jnlp.signed=false
+jnlp.signing=
+jnlp.signing.alias=
+jnlp.signing.keystore=
+# Main class for Java launcher
+main.class=com.javafx.main.Main
+manifest.file=manifest.mf
+meta.inf.dir=${src.dir}/META-INF
+mkdist.disabled=false
+platform.active=JDK_1.7
+run.classpath=\
+ ${dist.jar}:\
+ ${javac.classpath}
+run.test.classpath=\
+ ${javac.test.classpath}:\
+ ${build.test.classes.dir}
+source.encoding=UTF-8
+src.dir=src
+test.src.dir=test
diff --git a/nbproject/project.xml b/nbproject/project.xml
new file mode 100644
index 0000000..a7e0899
--- /dev/null
+++ b/nbproject/project.xml
@@ -0,0 +1,29 @@
+
+
+ org.netbeans.modules.java.j2seproject
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ BlogReader
+
+
+
+
+
+
+
+
+
+
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..e628294
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,4 @@
+This is the BlogReader in JavaFX 2.
+
+It should look similat to the blogreader in [Flex 2](http://www.crydust.be/blog/2006/12/26/blogreader/) and in [AsWing](http://www.crydust.be/blog/2007/05/11/blogreader-in-aswing/). This project is only meant as a coding exercise.
+
diff --git a/src/blogreader/BlogReader.java b/src/blogreader/BlogReader.java
new file mode 100644
index 0000000..a29371d
--- /dev/null
+++ b/src/blogreader/BlogReader.java
@@ -0,0 +1,36 @@
+package blogreader;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+/**
+ *
+ * @author kristof
+ */
+public class BlogReader extends Application {
+
+ @Override
+ public void start(Stage stage) throws Exception {
+ Parent root = FXMLLoader.load(getClass().getResource("View.fxml"));
+
+ Scene scene = new Scene(root);
+
+ stage.setScene(scene);
+ stage.show();
+ }
+
+ /**
+ * The main() method is ignored in correctly deployed JavaFX application.
+ * main() serves only as fallback in case the application can not be
+ * launched through deployment artifacts, e.g., in IDEs with limited FX
+ * support. NetBeans ignores main().
+ *
+ * @param args the command line arguments
+ */
+ public static void main(String[] args) {
+ launch(args);
+ }
+}
diff --git a/src/blogreader/Controller.java b/src/blogreader/Controller.java
new file mode 100644
index 0000000..3b79d8e
--- /dev/null
+++ b/src/blogreader/Controller.java
@@ -0,0 +1,238 @@
+package blogreader;
+
+import blogreader.model.FeedItem;
+import blogreader.model.Feed;
+import static blogreader.util.FeedLoader.loadDocument;
+import static blogreader.util.FeedLoader.readTitle;
+import static blogreader.util.FeedLoader.readItems;
+import com.sun.webpane.webkit.JSObject;
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.List;
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javafx.application.Platform;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+import javafx.concurrent.Task;
+import javafx.concurrent.Worker;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.fxml.Initializable;
+import javafx.scene.control.Label;
+import javafx.scene.web.WebEngine;
+import javafx.scene.web.WebView;
+import javafx.concurrent.Worker.State;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableColumn.CellDataFeatures;
+import javafx.scene.control.TableView;
+import javafx.scene.control.cell.PropertyValueFactory;
+import javafx.util.Callback;
+import org.w3c.dom.Document;
+
+/**
+ *
+ * @author kristof
+ */
+public class Controller implements Initializable {
+
+ private static final Logger logger = Logger.getLogger(Controller.class.getName());
+ private static final String FEED_URL = "http://www.crydust.be/blog/feed/";
+ private static final String TITLE_COLUMN_TEXT = "Title";
+ private static final String DATE_COLUMN_TEXT = "Date";
+ private static final String DATE_FORMAT = "dd/MM/yyyy";
+ private static final String TITLE_PROPERTY_NAME = "title";
+ private static final String WEBVIEW_TEMPLATE = ""
+ + "\n"
+ + ""
+ + "%s";
+ @FXML
+ private Label titleLabel;
+ @FXML
+ private TableView itemsTableView;
+ @FXML
+ private WebView itemWebView;
+ private final Feed model = new Feed();
+
+ @Override
+ public void initialize(URL url, ResourceBundle rb) {
+
+ logger.log(Level.INFO, "initialize");
+
+ // initialize label
+ titleLabel.textProperty().bind(model.titleProperty());
+
+ // initialize table
+ // * add two columns
+ // * bind the model
+ // * add selection listener
+ TableColumn titleColumn = new TableColumn();
+ titleColumn.setText(TITLE_COLUMN_TEXT);
+ titleColumn.setPrefWidth(400);
+ titleColumn.setCellValueFactory(new PropertyValueFactory(TITLE_PROPERTY_NAME));
+
+ TableColumn pubDateColumn = new TableColumn();
+ pubDateColumn.setText(DATE_COLUMN_TEXT);
+ pubDateColumn.setPrefWidth(200);
+ pubDateColumn.setCellValueFactory(new Callback, ObservableValue>() {
+ @Override
+ public ObservableValue call(CellDataFeatures p) {
+ DateFormat df = new SimpleDateFormat(DATE_FORMAT);
+ return new SimpleStringProperty(df.format(p.getValue().getPubDate()));
+ }
+ });
+
+ itemsTableView.getColumns().add(titleColumn);
+ itemsTableView.getColumns().add(pubDateColumn);
+ itemsTableView.itemsProperty().bind(model.itemsProperty());
+ itemsTableView.getSelectionModel().selectedItemProperty()
+ .addListener(new SelectionChangeListener(itemWebView));
+
+ // initialize webview
+ // * disable the contextmenu
+ // * listen to statechanges
+ itemWebView.setContextMenuEnabled(false);
+ final WebEngine webEngine = itemWebView.getEngine();
+ webEngine.getLoadWorker().stateProperty().addListener(
+ new ChangeListener() {
+ @Override
+ public void changed(ObservableValue extends State> p, State oldState, State newState) {
+ if (newState == Worker.State.SUCCEEDED) {
+ // add a bridge from the javascript to the java world
+ // run javascript init function
+ JSObject win = (JSObject) webEngine.executeScript("window");
+ win.setMember("javaObj", new Bridge());
+ webEngine.executeScript("init()");
+ }
+ }
+ });
+
+ // fetch data in the background
+ logger.log(Level.INFO, "Begin loading feed");
+ new Thread(new Task() {
+ @Override
+ protected Document call() throws Exception {
+ return loadDocument(FEED_URL);
+ }
+
+ @Override
+ protected void succeeded() {
+ super.succeeded();
+ model.itemsProperty().clear();
+ Document doc = this.getValue();
+ if (doc != null) {
+ final String title = readTitle(doc);
+ final List items = readItems(doc);
+ if (title != null || items != null) {
+ logger.log(Level.INFO, "Feed loaded and read");
+ model.titleProperty().set(title);
+ model.itemsProperty().addAll(items);
+ itemsTableView.getSelectionModel().selectFirst();
+ } else {
+ logger.log(Level.SEVERE, "Error reading feed");
+ model.titleProperty().set("Error reading feed");
+ }
+ } else {
+ logger.log(Level.SEVERE, "Error loading feed");
+ model.titleProperty().set("Error loading feed");
+ }
+ }
+ }).start();
+
+ }
+
+ /**
+ * Convenience method to load a web page in the system default browser.
This in a separate thread obviously.
+ *
+ * @param url
+ */
+ private static void browse(final String url) {
+ Platform.runLater(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ Desktop.getDesktop().browse(new URI(url));
+ } catch (IOException | URISyntaxException ex) {
+ logger.log(Level.SEVERE, null, ex);
+ }
+ }
+ });
+ }
+
+ /**
+ * Event handler for the button.
Loads the item link in a browser.
+ *
+ * @param event
+ */
+ @FXML
+ void onLinkButtonClick(ActionEvent event) {
+ browse(itemsTableView.getSelectionModel().selectedItemProperty().get().getLink());
+ }
+
+ /**
+ * Bridge class
Allows the webview from loading urls in the default
+ * browser
+ */
+ private static class Bridge {
+
+ private static final Logger logger = Logger.getLogger(Bridge.class.getName());
+
+ public Bridge() {
+ }
+
+ public void browse(final String url) {
+ Controller.browse(url);
+ }
+
+ public void log(final String message) {
+ logger.log(Level.INFO, message);
+ }
+ }
+
+ /**
+ * Load the items description when the selected item changes.
The
+ * stylesheet makes the text legible.
The script captures clicks on
+ * every link.
+ */
+ private static class SelectionChangeListener implements ChangeListener {
+
+ private final WebView webView;
+
+ public SelectionChangeListener(final WebView webView) {
+ this.webView = webView;
+ }
+
+ @Override
+ public void changed(ObservableValue extends FeedItem> observable,
+ FeedItem oldValue, FeedItem newValue) {
+ webView.getEngine().loadContent(String.format(WEBVIEW_TEMPLATE,
+ newValue.getDescription()));
+ }
+ }
+}
diff --git a/src/blogreader/FeedRequest.java b/src/blogreader/FeedRequest.java
new file mode 100644
index 0000000..0cacbfa
--- /dev/null
+++ b/src/blogreader/FeedRequest.java
@@ -0,0 +1,193 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package blogreader;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ *
+ * @author kristof
+ */
+public class FeedRequest {
+
+ private static final Logger logger = Logger.getLogger(FeedRequest.class.getName());
+ private final String title;
+ private final List- items;
+
+ public FeedRequest() {
+ Document doc = loadDocument("http://www.crydust.be/blog/feed/");
+ title = readTitle(doc);
+ items = readItems(doc);
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public List
- getItems() {
+ return items;
+ }
+
+ private static Document loadDocument(final String spec) {
+ Document doc = null;
+ InputStream in = null;
+ try {
+ URL url = new URL(spec);
+ URLConnection urlc = url.openConnection();
+ urlc.addRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1)");
+ in = urlc.getInputStream();
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ DocumentBuilder builder;
+ builder = factory.newDocumentBuilder();
+ doc = builder.parse(in);
+ } catch (SAXException | ParserConfigurationException | IOException ex) {
+ logger.log(Level.SEVERE, null, ex);
+ } finally {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ } catch (IOException ex) {
+ logger.log(Level.SEVERE, null, ex);
+ }
+ }
+ return doc;
+ }
+
+ private static Object readObject(final Node doc, final QName qname, final String expression) {
+ Object result = null;
+ if (doc != null) {
+ try {
+ XPathExpression expr = null;
+ XPathFactory xFactory = XPathFactory.newInstance();
+ XPath xpath = xFactory.newXPath();
+ expr = xpath.compile(expression);
+ result = expr.evaluate(doc, qname);
+ } catch (XPathExpressionException ex) {
+ logger.log(Level.SEVERE, null, ex);
+ }
+ } else {
+ logger.log(Level.SEVERE, "doc is null");
+ }
+ return result;
+ }
+
+ private static String readString(final Node doc, final String expression) {
+ String result = "?";
+ Object object = readObject(doc, XPathConstants.STRING, expression);
+ if (object instanceof String) {
+ result = (String) object;
+ } else {
+ logger.log(Level.SEVERE, "doc is null");
+ }
+ return result;
+ }
+
+ private static NodeList readNodeList(final Node doc, final String expression) {
+ NodeList result = null;
+ Object object = readObject(doc, XPathConstants.NODESET, expression);
+ if (object instanceof NodeList) {
+ result = (NodeList) object;
+ } else {
+ logger.log(Level.SEVERE, "doc is null");
+ }
+ return result;
+ }
+
+ private static String readTitle(final Node doc) {
+ return readString(doc, "/rss/channel/title/text()");
+ }
+
+ private static List
- readItems(final Node doc) {
+ NodeList itemNodes = readNodeList(doc, "/rss/channel/item");
+ int leni = itemNodes.getLength();
+ List
- items = new ArrayList<>(leni);
+ for (int i = 0; i < leni; i++) {
+ Node itemNode = itemNodes.item(i);
+ Item item = new Item(
+ readString(itemNode, "./title/text()"),
+ parseDateRfc822(readString(itemNode, "./pubDate/text()")),
+ readString(itemNode, "./link/text()"),
+ readString(itemNode, "./description/text()"));
+ items.add(item);
+ System.out.println(item);
+ }
+ return Collections.unmodifiableList(items);
+ }
+
+ private static Date parseDateRfc822(final String s) {
+ Date date = null;
+ SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.US);
+ try {
+ date = format.parse(s);
+ } catch (ParseException ex) {
+ logger.log(Level.SEVERE, null, ex);
+ }
+ return date;
+ }
+
+ public static class Item {
+
+ private final String title;
+ private final Date pubDate;
+ private final String link;
+ private final String description;
+
+ public Item(final String title, final Date pubDate, final String link, final String description) {
+ this.title = title;
+ this.pubDate = pubDate;
+ this.link = link;
+ this.description = description;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public Date getPubDate() {
+ return pubDate;
+ }
+
+ public String getLink() {
+ return link;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public String toString() {
+ return "Item{" + "title=" + title + ", pubDate=" + pubDate + '}';
+ }
+
+ }
+}
diff --git a/src/blogreader/View.fxml b/src/blogreader/View.fxml
new file mode 100644
index 0000000..d4f21ca
--- /dev/null
+++ b/src/blogreader/View.fxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/blogreader/model/Feed.java b/src/blogreader/model/Feed.java
new file mode 100644
index 0000000..f2e7a41
--- /dev/null
+++ b/src/blogreader/model/Feed.java
@@ -0,0 +1,49 @@
+package blogreader.model;
+
+import javafx.beans.property.ListProperty;
+import javafx.beans.property.SimpleListProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+/**
+ *
+ * @author kristof
+ */
+public class Feed {
+
+ private final StringProperty title;
+ private final ObservableList itemsList;
+ private final ListProperty items;
+
+ public Feed() {
+ title = new SimpleStringProperty("Loading ...");
+ itemsList = FXCollections.observableArrayList();
+ items = new SimpleListProperty<>(this, "items", itemsList);
+ }
+
+ public String getTitle() {
+ return title.get();
+ }
+
+ public void setTitle(String value) {
+ title.set(value);
+ }
+
+ public StringProperty titleProperty() {
+ return title;
+ }
+
+ public ObservableList getItems() {
+ return items.get();
+ }
+
+ public void setItems(ObservableList value) {
+ items.set(value);
+ }
+
+ public ListProperty itemsProperty() {
+ return items;
+ }
+}
diff --git a/src/blogreader/model/FeedItem.java b/src/blogreader/model/FeedItem.java
new file mode 100644
index 0000000..138ab51
--- /dev/null
+++ b/src/blogreader/model/FeedItem.java
@@ -0,0 +1,43 @@
+package blogreader.model;
+
+import java.util.Date;
+
+/**
+ *
+ * @author kristof
+ */
+public class FeedItem {
+
+ private final String title;
+ private final Date pubDate;
+ private final String link;
+ private final String description;
+
+ public FeedItem(final String title, final Date pubDate, final String link, final String description) {
+ this.title = title;
+ this.pubDate = pubDate;
+ this.link = link;
+ this.description = description;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public Date getPubDate() {
+ return pubDate;
+ }
+
+ public String getLink() {
+ return link;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ @Override
+ public String toString() {
+ return "Item{" + "title=" + title + ", pubDate=" + pubDate + '}';
+ }
+}
diff --git a/src/blogreader/style.css b/src/blogreader/style.css
new file mode 100644
index 0000000..6f4a304
--- /dev/null
+++ b/src/blogreader/style.css
@@ -0,0 +1,3 @@
+#title {
+ -fx-font-size: 20;
+}
\ No newline at end of file
diff --git a/src/blogreader/util/FeedLoader.java b/src/blogreader/util/FeedLoader.java
new file mode 100644
index 0000000..e6a23ba
--- /dev/null
+++ b/src/blogreader/util/FeedLoader.java
@@ -0,0 +1,139 @@
+package blogreader.util;
+
+import blogreader.model.FeedItem;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.xml.namespace.QName;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpression;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ *
+ * @author kristof
+ */
+public abstract class FeedLoader {
+
+ private static final Logger logger = Logger.getLogger(FeedLoader.class.getName());
+
+ private FeedLoader() {
+ }
+
+ public static Document loadDocument(final String spec) {
+ Document doc = null;
+ InputStream in = null;
+ try {
+ URL url = new URL(spec);
+ URLConnection urlc = url.openConnection();
+ urlc.addRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 6.0; Windows NT 5.1)");
+ in = urlc.getInputStream();
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ DocumentBuilder builder;
+ builder = factory.newDocumentBuilder();
+ doc = builder.parse(in);
+ } catch (SAXException | ParserConfigurationException | IOException ex) {
+ logger.log(Level.SEVERE, null, ex);
+ } finally {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ } catch (IOException ex) {
+ logger.log(Level.SEVERE, null, ex);
+ }
+ }
+ return doc;
+ }
+
+ public static String readTitle(final Node doc) {
+ return readString(doc, "/rss/channel/title/text()");
+ }
+
+ public static List readItems(final Node doc) {
+ NodeList itemNodes = readNodeList(doc, "/rss/channel/item");
+ int leni = itemNodes.getLength();
+ List items = new ArrayList<>(leni);
+ for (int i = 0; i < leni; i++) {
+ Node itemNode = itemNodes.item(i);
+ FeedItem item = new FeedItem(
+ readString(itemNode, "./title/text()"),
+ parseDateRfc822(readString(itemNode, "./pubDate/text()")),
+ readString(itemNode, "./link/text()"),
+ readString(itemNode, "./description/text()"));
+ items.add(item);
+ }
+ return Collections.unmodifiableList(items);
+ }
+
+ private static Object readObject(final Node doc, final QName qname, final String expression) {
+ Object result = null;
+ if (doc != null) {
+ try {
+ XPathExpression expr = null;
+ XPathFactory xFactory = XPathFactory.newInstance();
+ XPath xpath = xFactory.newXPath();
+ expr = xpath.compile(expression);
+ result = expr.evaluate(doc, qname);
+ } catch (XPathExpressionException ex) {
+ logger.log(Level.SEVERE, null, ex);
+ }
+ } else {
+ logger.log(Level.SEVERE, "doc is null");
+ }
+ return result;
+ }
+
+ private static String readString(final Node doc, final String expression) {
+ String result = null;
+ Object object = readObject(doc, XPathConstants.STRING, expression);
+ if (object instanceof String) {
+ result = (String) object;
+ } else {
+ logger.log(Level.SEVERE, "doc is null");
+ }
+ return result;
+ }
+
+ private static NodeList readNodeList(final Node doc, final String expression) {
+ NodeList result = null;
+ Object object = readObject(doc, XPathConstants.NODESET, expression);
+ if (object instanceof NodeList) {
+ result = (NodeList) object;
+ } else {
+ logger.log(Level.SEVERE, "doc is null");
+ }
+ return result;
+ }
+
+ private static Date parseDateRfc822(final String s) {
+ Date date = null;
+ SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.US);
+ try {
+ date = format.parse(s);
+ } catch (ParseException ex) {
+ logger.log(Level.SEVERE, null, ex);
+ }
+ return date;
+ }
+}