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 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 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 @@ + + + + + + + + + + + + +