JavaでRSSの広告削除してみる(3) Removing Ads from Rss by Java

RSSから広告削除する、簡単なアプリをつくってみようかと思っています。
3回に分けて書いています。I write this in 3-posts.

第1回 JavaでRSS受信する。 Getting RSS by Java
第2回 HTMLパーサを使う。 Using HTML-parser
第3回 RSSを出力してみる。 Outputting RSS

今回は第3回めです。This is 3rd post.

————————————————————–
使っている環境

Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1
Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

ROME 1.0
JDOM 1.1.3
( Xerces 2.11.0 )
jsoup-1.7.2


(1)はじめに Introduction
クラスを作ってその中で「RSS読込→広告削除→RSS出力」の処理を行いたいと思います。
RSS読込・広告削除は、前回・前々回のエントリーで行ったので、今回は、まずRSS出力を試してみます。

In my Class, I would try to do entire process of “RSS-reading”, “removing-Ads”, and “RSS-outputting”.
I did “RSS-reading” and “removing-Ads” in previously post. Today, I will try to do “RSS-outputting” first.


(2)RSSの出力 Outputting RSS
SyndFeedOutput クラスのoutputString メソッドで、XMLを出力できます。
ROMEでRSSを読込んだ時のfeedを、outputString メソッドに引き渡す感じになります。
You could output XML by using outputString method of SyndFeedOutput class.
You execute outputString method with the “feed” that made by ROME .
(参考) 技術への名残り: JavaでRSSを作成(ROME利用)

        SyndFeedOutput output = new SyndFeedOutput();
        String outputRss = output.outputString(feed);

(3)コーディング Coding
CrawlRssクラスを作って、その中で処理を行うことにしました。
処理の流れはこんな感じ。
I thought that I would make “CrawlRss class” and do what’s I need.
I will show you my plan(below)

・setUrlメソッド
接続先URLをセットします。Setting URL

・getRssFeedメソッド
RSSを読込みます。このメソッドは、取得したデータ(SyndFeed型)を返します。
Reading RSS. this method will return that data as SyndFeed-type.

・doFilterメソッド
RSSのタイトルが、広告かどうかをチェックして、広告の場合は削除します。
RSSのdescriptionをチェックして、中に含まれる広告を削除します。
(広告かどうか判別する処理は、今後も増えていくため、別メソッドに書いています。
タイトルのチェックはtitleIsAdsメソッド、descriptionのチェックはhtmlParsingメソッドの中で、具体的なチェックを行っています。)

Checking “Title” of RSS-entry. and If the Title is Ads, it’s remove the RSS-entry.
Checking “description” of RSS-entry. And it’s remove Ads in the description.

・outputRssメソッド
RSSfeedデータから、RSSを出力します。String型でXMLデータを返します。
This output RSS generated from RSSfeed-data. This return RSS as XML-data.

CrawlRss.java

package rssReader;

import java.io.IOException;
import java.net.URL;
import java.util.Iterator;
import java.util.List;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.SyndFeedOutput;
import com.sun.syndication.io.XmlReader;
import com.sun.syndication.feed.synd.SyndContent; 

public class CrawlRss {
    private String url;

    /** 
     * XXX 
     *
     * @param  url  URL for getting RssFeed
     * @param  feed  RssFeed generated by ROME
     */
    public void setUrl(String url) {
        this.url = url;
    }

    public String getUrl() {
        return this.url;
    }

    public SyndFeed getRssFeed() throws IOException  {
        SyndFeed feed = null;

        if ( this.url == null ){
            System.out.println("Parameter( url ) didn't set.");
            return null;
        }else{
            try {
                URL feedUrl = new URL(this.url);
                XmlReader reader = new XmlReader(feedUrl.openStream());
                    SyndFeedInput input = new SyndFeedInput();
                    feed = input.build(reader);
                reader.close();

            } catch (FeedException e) {
                e.printStackTrace();
            }
        }

        return feed;
    }

    public SyndFeed doFilter(SyndFeed feed){

        List<SyndEntry> entries = feed.getEntries();
        Iterator<SyndEntry> itr = entries.iterator();

        while (itr.hasNext()){
            SyndEntry entry = itr.next();

            // Removing Ads from Title
            String title = entry.getTitle();
            if ( titleIsAds(title)) {
                itr.remove();
                continue;
            }

            // Removing Ads from Desc
            SyndContent desc = entry.getDescription();

            if (desc != null){
                String html_parsed = htmlParsing(desc.getValue());
                desc.setValue(html_parsed);
            }
        }

        return feed;
    }

    public boolean titleIsAds(String title){
        if ( title.startsWith("PR:")) {
            return true;
        }else{
            return false;
        }

    }

    public String htmlParsing(String html){

        Document doc = Jsoup.parse(html, "UTF-8");
        Elements links = doc.getElementsByTag("a");

        for (Element link : links) {
            String href = link.attr("href");

            if ( href.indexOf("rss.rssad.jp/rss/ad/") != -1 ) {
                link.remove();
            }
        }

        Elements imgs = doc.getElementsByTag("img");

        for (Element img : imgs) {
            String width = img.attr("width");
            String height = img.attr("height");

            if ( width.equals("1") && height.equals("1") ) {
                img.remove();
            }
        }

        String bodies_str = doc.getElementsByTag("body").toString();
        bodies_str = bodies_str.replaceFirst("^<body>\n", "");
        bodies_str = bodies_str.replaceFirst("\n</body>$", "");
        bodies_str = bodies_str.replaceFirst("^ +", "");

        return bodies_str;
    }

    public String outputRss(SyndFeed feed) throws FeedException{
        SyndFeedOutput output = new SyndFeedOutput();
        String outputRss = output.outputString(feed);

    return outputRss;
    }
}

実際に動かす場合は、こうします。
Here is my code to use the Class.

package rssReader.test;

import java.io.IOException;

import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.FeedException;

import rssReader.CrawlRss;

public class unitTest_output_XML {
    public static void main(String[] args) throws IOException, FeedException {

        CrawlRss crss = new CrawlRss();

        crss.setUrl("http://rss.rssad.jp/rss/gihyo/feed/rss2");
        // crss.setUrl("http://jp.techcrunch.com/feed/");
        // crss.setUrl("http://wired.jp/rssfeeder/");

        //System.out.println("Access to : " + crss.getUrl() + "\n\n");

        SyndFeed feed = crss.getRssFeed();

        if (feed == null){
            System.out.println("Exit");
            System.exit(-1);
        }

        feed = crss.doFilter(feed);

        String outputRss = crss.outputRss(feed);
        System.out.println(outputRss);

    }

}

こんな感じでXMLが返ってきます
That code will return these XML(below).

<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
  <channel>
    <title>gihyo.jp:総合</title>
    <link>http://gihyo.jp/</link>
    <description>gihyo.jp(総合)の更新情報をお届けします</description>
    <language>ja-jp</language>
    <copyright>技術評論社 2013</copyright>
   :   :

    <item>
      <title>第6回 Capacity Scheduler による複数ジョブの同時実行 ── halookで始めるHadoop/HBaseトラブルシューティング</title>
      <link>http://rss.rssad.jp/rss/artclk/47ghQF72z411/6d70ced8b37632c22bd6add0ae4e4982?ul=tMUp6phKmaEl5PP2lXYGvZcEI0UboxMPKoYv1ONSRKgTtDvugPJtMP3M.QKxrTI5bkjb3.g</link>
      <description>今回は,Capacity Schedulerを使った場合について,複数ジョブの同時実行時の挙動を見てみましょう。</description>
   :   :

    </item>
    <item>
   :   :

今のところ、TechCrunch Japan、WIRED.jp、gihyo.jp のRSSfeedから広告削除できるようになっています。もうちょっと、チェックの条件を増やして、たいていのRSSに対応できるような形にできればいいなと、思っています。
This code supports removing Ads from RSS of “TechCrunch Japan”, “WIRED.jp” “gihyo.jp”.
I hope that I would add more rule to the code for removing Ads, so it could support more RSS.

それをGoogleAppEngine上で動かすようにすれば、APIとして公開できるかな。(同じようなサービスは、たくさんあるけど(笑))
And then,  I would deploy the code on GAE as an API. (There is many Service for Romoving Ads  from RSS 🙂  )

JavaでRSSの広告削除してみる(2) Removing Ads from Rss by Java

RSSから広告削除する、簡単なアプリをつくってみようかと思っています。
3回に分けて書いています。I write this in 3-posts.

第1回 JavaでRSS受信する。 Getting RSS by Java
第2回 HTMLパーサを使う。 Using HTML-parser
第3回 RSSを出力してみる。 Outputting RSS

今回は第2回めです。This is 2nd post.

————————————————————–
使っている環境

Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1
Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

jsoup-1.7.2


(1)はじめに Introduction
実際にRSS取得してみると、記事丸ごとが広告になっている場合と、要約の中に広告が埋まっている場合があります。
たとえば、タイトルが”PR:”で始まっているものは、記事全体が広告になってる。これはタイトルの文字列をチェックするだけなので取り除くのは簡単です。
I get RSS, then I found that some article are Ads, and some description contain Ads.
For example, if “Title” start with “PR:”, this articl is Ads. Checking Title is easy.

PR: 医療機関ホームページの虚偽・誇大な表現はNGです!

要約の中に広告が埋まっている場合は、以下のような形で、広告のリンクが埋まっています。
また、サイズが1×1の画像ファイルが埋め込まれていたりします。これも要らない。
This is example that a description contain “link to Ads”.  And there is “img” that it has 1x1size. this “img” have to be removed too.

<p>前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。
<img border="0" width="1" height="1" src="http://rss.rssad.jp/rss/artimg/47ghQF72z411/3184e1aa6c761b751f68c2613f8a3f55"/></p>
<br clear="all" /><a href="http://rss.rssad.jp/rss/ad/47ghQF72z411/pFjyc8QFo.Eh?type=1" target="_blank"><img src="http://rss.rssad.jp/rss/img/47ghQF72z411/pFjyc8QFo.Eh?type=1" border="0"/></a><br/>

これらを取り除く場合、HTMLをParseする必要があるため、HTMLパーサのライブラリを使い、不要な部分のみを取り除くことにします。
You should use HTMP-parsing-library to remove these words.


(2)jsoupのダウンロード Downloading jsoup
そもそもHTMLのパーサは、たくさんあります。There are many library.
Comparison of HTML parsers – Wikipedia, the free encyclopedia

活発に開発されていてHTML5にも対応しているものがいいと思いました。
jsoupはHTML5にも対応しており、jQuery-likeなやり方でも使えるとのことで、なかなかよさそうです。
なお、Unitテストといったことを行いたい場合は、HtmlUnitがいいみたいです。
I should use the library that is developed more active , and support  HTML5.
“jsoup” support HTML5, and jQuery-like method. I think it’s ok.
If you wish to do Unit-test , HtmlUnit would be suitable.
What are the pros and cons of the leading Java HTML parsers? – Stack Overflow
HtmlUnit vs JSoup: HTML Parsing in Java | Javalobby

今回はHTMLのparsingが目的なので、jsoupを使うことにしました。
こちらのサイトから、jsoup-1.7.2.jar をダウンロードし、Eclipseのライブラリに追加します。
I wish to use library for parsing HTML. So I chose jsoup.
you can download jsoup-1.7.2.jar in this site. And you should add it to Eclipse-library .
jsoup Java HTML Parser, with best of DOM, CSS, and jquery


(3)とりあえず読込んでみる Reading simply
テストデータとして以下の内容を、テキストファイル(testdata_html.txt)に書き込んでおき、jsoupを動かしてみました。
I tried to use jsoup. I made test-data in a text file(testdata_html.txt). I show you the test-data below.

<p>前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。
<img border="0" width="1" height="1" src="http://rss.rssad.jp/rss/artimg/47ghQF72z411/3184e1aa6c761b751f68c2613f8a3f55"/></p>
<br clear="all" /><a href="http://rss.rssad.jp/rss/ad/47ghQF72z411/pFjyc8QFo.Eh?type=1" target="_blank"><img src="http://rss.rssad.jp/rss/img/47ghQF72z411/pFjyc8QFo.Eh?type=1" border="0"/></a><br/>

基本的に公式ドキュメントがとてもわかりやすかったので、それを参考にしました。
I check this documents for use .
Cookbook: jsoup Java HTML parser

jsoupでテストデータを読込むと、不足している要素・タグが追加され、HTMLファイルの構成で、内部に格納されます。<html>タグや、<head>タグが追加されます。
When jsoup get test-data, jsoup make entire HTML elements. If test-data didn’t have “<html>-tag” or “<head>-tag”, jsoup would add these tags.

parsingした結果をブラウザで確認してみようと思ったので、<head>タグ内に文字コード設定を追加することにしました。
で、とりあえずテストデータを読込んで、それをそのままjsoupで出力させるコードを以下に書きます。
I think I practised jsoup and I would check the result by Browser. So I added char-setting in “<head>-tag”.
If you execute this code, it’s show you the result of “Reading RSS” and “outputting RSS-parsing-data”.

HtmlParsing.java

import java.io.File;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

public class HtmlParsing {
    public static void main(String[] args) throws Exception {

        File input = new File("testdata_html.txt");
        Document doc = Jsoup.parse(input, "UTF-8");

        Element head = doc.select("head").first();
        head.html("<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">");

        System.out.println("--- doc -----");
        System.out.println(doc + "\n");

        System.out.println("--- doc text only-----");
        System.out.println(doc.text() + "\n");

    }
}

実行結果は、こんな感じになります。
You can get this when you execute that code(above).

--- doc -----
<html>
 <head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
 </head>
 <body>
  <p>前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。 <img border="0" width="1" height="1" src="http://rss.rssad.jp/rss/artimg/47ghQF72z411/3184e1aa6c761b751f68c2613f8a3f55" /></p> 
  <br clear="all" />
  <a href="http://rss.rssad.jp/rss/ad/47ghQF72z411/pFjyc8QFo.Eh?type=1" target="_blank"><img src="http://rss.rssad.jp/rss/img/47ghQF72z411/pFjyc8QFo.Eh?type=1" border="0" /></a>
  <br />
 </body>
</html>

--- doc text only-----
前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。

(4)Parsing
広告の削除と、サイズが1×1の画像ファイルの削除を行います。
I will remove Ads and img-files that has 1x1size.

<a>タグの一覧を取得し、それぞれのAttribute”href”の値を取得します。その中に”rss.rssad.jp/rss/ad/”が含まれていたら、<a>タグごと削除します。これで広告の削除ができます。
This code gets <a>-tags and each “href” Attribute. If there are the words “rss.rssad.jp/rss/ad/”, it’s remove entire <a>-tag. Then you could remove ads.

また、<img>タグの一覧を取得し、それぞれのAttribute”width”、”height”の値を取得します。width=height=1の場合、<img>タグごと削除します。
And it’s gets <img>-tags, each “width” Attribute and each “height” Attribute. If those Attribute are width=height=1, you chould remove entire <img>-tag.

ソースコードは、こんな感じになりました。
this is my code.

HtmlParsing2.java

import java.io.File;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

public class HtmlParsing2 {
    public static void main(String[] args) throws Exception {

        File input = new File("testdata_html.txt");
        Document doc = Jsoup.parse(input, "UTF-8");

        Element head = doc.select("head").first();
        head.html("<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">");

        Elements links = doc.getElementsByTag("a");

        for (Element link : links) {
            String href = link.attr("href");

            // System.out.println("href : " + href);
            // System.out.println("link.text : " + link.text());
            // System.out.println("link : " + link);

            if ( href.indexOf("rss.rssad.jp/rss/ad/") != -1 ) {
                link.remove();
            }
        }

        Elements imgs = doc.getElementsByTag("img");

        for (Element img : imgs) {
            String width = img.attr("width");
            String height = img.attr("height");
            // System.out.println("img : " + img);

            if ( width.equals("1") && height.equals("1") ) {
                img.remove();
            }
        }

        System.out.println("--- doc2 -----");
        System.out.println(doc + "\n");
        System.out.println("--- doc text only-----");
        System.out.println(doc.text() + "\n");
        System.out.println("--- body-----");
        System.out.println(doc.getElementsByTag("body") + "\n");
    }
}

実行結果は、こんな感じ。
I will how you the result.

--- doc2 -----
<html>
 <head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
 </head>
 <body>
  <p>前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。 </p> 
  <br clear="all" />
  <br />
 </body>
</html>

--- doc text only-----
前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。

--- body-----
<body>
 <p>前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。 </p> 
 <br clear="all" />
 <br />
</body>

これで、やりたいことはできました。
Then I did.

最終的にRSSのフィードに戻すときに、<body>, </body>タグが不要になります。なので、以下のようにして、Elements型を文字列にしてから取り除くことにします。(※不要な改行と、先頭の空白文字も取り除いています。)
When you return this result to RSS-feed, <body>tag and </body>-tag didin’t need.
So I transfered type-“elements” to String, and then removed these elements . ( I removed “\n” and “blanks at the head of line”)

     :   :
        String bodies_str = doc.getElementsByTag("body").toString();
        bodies_str = bodies_str.replaceFirst("^<body>\n", "");
        bodies_str = bodies_str.replaceFirst("\n</body>$", "");
        bodies_str = bodies_str.replaceFirst("^ +", "");
     :   :

とりあえず、これで下準備がおわりました。
次回のエントリーでは、「RSS読込→広告削除→RSS出力」までつなげてみます。

In next post, I would try to do entire process of “RSS-reading”, “removing-Ads”, and “RSS-outputting”.

JavaでRSSの広告削除してみる(1) Removing Ads from Rss by Java

IT関連の情報サイトから、RSSフィードの情報収集して、その中の単語を解析・集計して、自分用のトレンドランキングをつくろうかと思った。
I come up with making “trend word list” for myself. I try to collect RSS from IT-website, analyze RSS-feed, and rank it.

ためしにRSS受信してみると、RSSフィードの中に広告が入っている場合がある。広告は削除しておきたい。Some RSS contain advertisement. I hope to remove Ads.

RSSから広告削除してくれるサービスがるんだけれども、There are some service to remove Ads from RSS.
RSS広告を削除ならRSS広告削除社

gihyo.jpのRSSの広告がうまく削除されなかった。But, that service didn’t remove Ads from “gihyo.jp”.
というわけで、自分でつくってしまおうかと思った。So I think of trying to make app.

3回に分けて書いていきます。I write this in 3-posts.

第1回 JavaでRSS受信する。 Getting RSS by Java
第2回 HTMLパーサを使う。 Using HTML-parser
第3回 RSSを出力してみる。 Outputting RSS

今回は第1回めです。This is first post.

————————————————————–
使っている環境

Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1
Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)

ROME 1.0
JDOM 1.1.3
( Xerces 2.11.0 )


(1)はじめに Introduction
JavaのRSSライブラリには、いろいろあるみたい。
There are many library for RSS-reading.
Java や Ruby の RSS/Atom feed ライブラリなど。HTMLからRSSを自動生成……したいなぁ。 (RSS – MemoWiki)

いくつか見てみたんだけど、RSSライブラリはあんまり活発な開発がされてない雰囲気。
どうも、RSSは、昔から使われていて仕様がかたまっているため、ライブラリの改修をする必要があんまり無いっぽい。
で、なんとなく情報量の多そうな、ROMEを使うことにした。
I search RSS-library. But some RSS-library is not active.
I think that RSS-format is so old, and changeless. So, it’s no need to modify RSS-library.


(2)ROME, JDOMのダウンロード  Downloading “ROME and JDOM”
こちらのサイトから、ROMEのダウンロードを行います。僕の場合、rome-1.0.jarをダウンロードしました。
You can download ROME from this site. I downloaded rome-1.0.jar.
Home – ROME – Confluence

ROMEはJDOMと依存関係があるため、こちらのサイトからJDOMもダウンロードします。
ROME have to use with JDOM. So, you should download JDOM from thi site.
JDOM

現時点でJDOM 2.0.5が最新版でしたが、こちらを使うとROMEがうまく動きませんでした。
なので、JDOM 1.1.3を使う必要があります。ダウンロードしたファイルjdom-1.1.3.zipの中のbuildフォルダに、jdom-1.1.3.jarがあるので、これをEclipseのライブラリに追加します。
“JDOM’s latest version 2.0.5” don’t work with ROME. You should use JDOM 1.1.3.
Download jdom-1.1.3.zip, then there is jdom-1.1.3.jar in “build” folder. And add jdom-1.1.3.jar to Eclipse-library.


(3)Xercesのダウンロード Downloading Xerces
こちらは、必要に応じて使うものになります。
単体のJavaアプリとして動かす際には必要ないですが、GAE上でROMEを動かそうとすると、以下のようなエラーが出ます。

Xerces dosn’t need, if you don’t make your Apps on GAE.
But if you use ROME on GAE, you would face this error.

java.lang.IllegalStateException: JDOM could not create a SAX parser
    at com.sun.syndication.io.WireFeedInput.createSAXBuilder(WireFeedInput.java:328)
    at com.sun.syndication.io.WireFeedInput.build(WireFeedInput.java:189)
    at com.sun.syndication.io.SyndFeedInput.build(SyndFeedInput.java:123)
   :   :
   :   :

原因は謎。こちらのサイトによると、Xercesが必要とのこと。
I don’t know Why this error has happen. According to these site, you need Xerces.
Romeが使えない – Google グループ
Issue 1367 – googleappengine – org.apache.xerces.parsers.SAXParser not available – Google App Engine – Google Project Hosting

そのため、こちらのサイトからXercesをダウンロードします。
You can download Xerces from this site.
The Apache Xerces™ Project – xerces.apache.org

ダウンロードしたファイル Xerces-J-bin.2.11.0.tar.gz の中にある、xercesImpl.jarと、xml-apis.jar をEclipseのライブラリに追加します。
Download Xerces-J-bin.2.11.0.tar.gz. In this file, you can get xercesImpl.jar and xml-apis.jar. Then you need to add these Jar-files into Eclipse-library.


(4)コーディング Coding
こちらのサイトを参考にしました。
This site will help you.
JavaのRSSライブラリ、ROMEを使ってみる
RSS・Atomフィードのためのライブラリ”ROME” – argius note
Kazuhiro’s Weblog: Javaで手軽にフィード(RSS、Atom)を扱うライブラリ 「ROME」

RSS取得時のコードは以下のような書き方になります。
You can get RSS with this code.

        String url = "http://jp.techcrunch.com/feed/";

        URL feedUrl = new URL(url);
        XmlReader reader = new XmlReader(feedUrl.openStream());
            SyndFeedInput input = new SyndFeedInput();
            SyndFeed feed = input.build(reader);
        reader.close();

上記では取得したデータが、feedの中に格納されています。
feed.getTitle()といった形で、個々のデータが取得できます。
RSSの1つ1つの記事は、feed.getEntries()でまとめて取得できます。List形式のデータになっています。

That code will give you RSS-data in  a variable “feed”.
You can get each a RSS-element using the method “feed.getTitle()”.
And you can get a RSS-entry using the method “feed.getEntries()”.

とりあえず、取得したデータをすべて表示してみることにしました。最終的なソースコードを以下にまとめて記載します。
I could show you each elements in RSS., If you use this code.

RomeSample3.java

import java.net.URL;
import java.util.List;

import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.io.SyndFeedInput;
import com.sun.syndication.io.XmlReader;

public class RomeSample3 {
    public static void main(String[] args) throws Exception {
        // String url = "http://rss.rssad.jp/rss/gihyo/feed/rss2";

        // String url = "http://rss.dailynews.yahoo.co.jp/fc/rss.xml";
        String url = "http://jp.techcrunch.com/feed/";
        // String url = "http://wired.jp/rssfeeder/";

        URL feedUrl = new URL(url);
        XmlReader reader = new XmlReader(feedUrl.openStream());
            SyndFeedInput input = new SyndFeedInput();
            SyndFeed feed = input.build(reader);
        reader.close();

        // ###########################################################
        System.out.println("----- Site Infomation ----------------------------------");
        System.out.println("SITE author : " + feed.getAuthor());
        System.out.println("SITE authors : " + feed.getAuthors());
        System.out.println("SITE categories : " + feed.getCategories());
        System.out.println("SITE contributors : " + feed.getContributors());
        System.out.println("SITE Copyright : " + feed.getCopyright());
        System.out.println("SITE description : " + feed.getDescription());
        System.out.println("SITE DescriptionEx : " + feed.getDescriptionEx());
        System.out.println("SITE encoding : " + feed.getEncoding());
        System.out.println("SITE feedType : " + feed.getFeedType());
        System.out.println("SITE ForeignMarkup : " + feed.getForeignMarkup());
        System.out.println("SITE image : " + feed.getImage());
        System.out.println("SITE language : " + feed.getLanguage());
        System.out.println("SITE link : " + feed.getLink());
        System.out.println("SITE links : " + feed.getLinks());
        // System.out.println("SITE Module : " + feed.getModule(url));
        // System.out.println("SITE Modules : " + feed.getModules());
        System.out.println("SITE getPublishedDate : " + feed.getPublishedDate());
        System.out.println("SITE getSupportedFeedTypes : " + feed.getSupportedFeedTypes());
        System.out.println("SITE title : " + feed.getTitle());
        System.out.println("SITE getTitleEx : " + feed.getTitleEx());
        System.out.println("SITE uri : " + feed.getUri());

        // ###########################################################
        List<SyndEntry> entries = feed.getEntries();

        Integer i = 1;
        for (SyndEntry entry : entries) {
            System.out.println("----- Entries No." + i + "----------------------------------");
            System.out.println("ENTRY Author : " + entry.getAuthor());
            System.out.println("ENTRY Authors : " + entry.getAuthors());
            System.out.println("ENTRY Categories : " + entry.getCategories());
            System.out.println("ENTRY Contents : " + entry.getContents());
            System.out.println("ENTRY Contributors : " + entry.getContributors());
            System.out.println("ENTRY Description : " + entry.getDescription());
            System.out.println("ENTRY Enclosures : " + entry.getEnclosures());
            System.out.println("ENTRY ForeignMarkup : " + entry.getForeignMarkup());
            System.out.println("ENTRY Link : " + entry.getLink());
            System.out.println("ENTRY Links : " + entry.getLinks());
            // System.out.println("ENTRY Module : " + entry.getModule(url));
            // System.out.println("ENTRY Modules : " + entry.getModules());
            System.out.println("ENTRY PublishedDate : " + entry.getPublishedDate());
            System.out.println("ENTRY Source : " + entry.getSource());
            System.out.println("ENTRY Title : " + entry.getTitle());
            System.out.println("ENTRY TitleEx : " + entry.getTitleEx());
            System.out.println("ENTRY UpdatedDate : " + entry.getUpdatedDate());
            System.out.println("ENTRY Uri : " + entry.getUri());
            System.out.println("ENTRY WireEntry : " + entry.getWireEntry());
            System.out.println("");

            i = i + 1;
        }
    }
}

これで、RSSの受信はできました。I could get RSS.

ほしい情報は、記事のタイトルと要約の部分になります。ソース上では以下の部分。
Title and Descriptin are important for me. I can get those using this method .

entry.getTitle()
entry.getDescription()

実際にRSS取得してみると、記事丸ごとが広告になっている場合と、要約の中に広告が埋まっている場合があります。
タイトルが”PR:”で始まっているものは、記事全体が広告になってる。これはタイトルの文字列をチェックするだけなので取り除くのは簡単です。
要約の中に広告が埋まっている場合は、以下のような形で、広告のリンクが埋まっています。

I got RSS-feed, then I found that there is some Ads in RSS. Some entry are ads, and some RSS-description has ads.
If “Title” start with “PR:” , RSS-entry is a advertisement. It’s easy to check “Title”.
But if RSS-description had ads, it’s difficult to remove ads.
This is sample that there are “link to ads” in description.

<p>前回紹介したように,gmtpに文字コード回りのパッチをあてることで,無事Plamo Linux環境とNexus 7の間でファイルをやりとりできるようになりました。しかしながら,あれこれ使っているとgmtpの欠点も目についてきました。
<img border="0" width="1" height="1" src="http://rss.rssad.jp/rss/artimg/47ghQF72z411/3184e1aa6c761b751f68c2613f8a3f55"/></p>
<br clear="all" /><a href="http://rss.rssad.jp/rss/ad/47ghQF72z411/pFjyc8QFo.Eh?type=1" target="_blank"><img src="http://rss.rssad.jp/rss/img/47ghQF72z411/pFjyc8QFo.Eh?type=1" border="0"/></a><br/>

この場合HTMLをParseする必要があるため、HTMLパーサのライブラリを使い、不要な部分のみを取り除くことにします。ここから先は、次回のエントリーで。
In this case, I have to parse HTML. So I will use HTML-parsing-library. And I will remove the link of ads.
I will show you that in next post.

GAEでSpringのValidation機能を使おうとしてハマった

GAE上でSpringMVCを動かしています。formからの入力にValidationを試してみました。で、どっぷりハマったので、とりあえず原因を速報。

“hibernate-validator 5.*” don’t work on GAE!!

————————————————————–

使っている環境

Java JDK1.6.0_35(64bit版)
Google App Engine SDK for Java 1.7.1
Eclipse IDE for Java EE Developers_ Juno (4.2)64Bit版
The Google Plugin for Eclipse, for Eclipse 3.8/4.2 (Juno)
Spring Framework 3.2.2.RELEASE
Commons Collections 3.2.1
Commons Lang 2.6
Commons Logging 1.1.2
Apache Velocity 1.7
hibernate-validator-4.3.0.Final ( + jboss-logging-3.1.0.CR2.jar, validation-api-1.0.0.GA.jar)


(1)NG編

まず、現時点での最新版である、
hibernate-validator 5.0.0.Final
を使ってみました。I try to use latest version “hibernate-validator 5.0.0.Final”.

ダウンロード先はこちら。
Downloads – JBoss Community

ダウンロードしたhibernate-validator-5.0.0.Final-dist.tar.gzの中にある、
hibernate-validator-5.0.0.Final.jar
validation-api-1.1.0.Final.jar
jboss-logging-3.1.1.GA.jar

をライブラリに追加して、アプリ起動、以下のエラーが出ました。NoClassFoundError has occured.

java.lang.NoClassDefFoundError: com/fasterxml/classmate/Filter

こちらのサイトから
SpringSource Enterprise Bundle Repository

FasterXML ClassMate 0.5.4をダウンロードして、com.springsource.com.fasterxml.classmate-0.5.4.jarをライブラリに追加しました。
アプリ起動してみると、またエラーが出ました。NoClassFoundError has occured again !

java.lang.ClassNotFoundException: de.odysseus.el.ExpressionFactoryImpl

そのため、今度はこちらのサイトから、
Java Unified Expression Language

juel-2.2.6をダウンロードしてきて、juel-impl-2.2.6.jarをライブラリに追加しました。
そうすると、アプリが正常に起動するようになりました。

しかし!

実際にValidation機能を作動させると、INTERNAL_SERVER_ERRORが発生。
このような謎のエラーが出てました。InternalServerError has occured !!

java.lang.ExceptionInInitializerError
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolateExpression(ResourceBundleMessageInterpolator.java:227)
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolateMessage(ResourceBundleMessageInterpolator.java:187)
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolate(ResourceBundleMessageInterpolator.java:120)
   :   :
   :   :

Caused by: javax.el.ELException: Could not create expression factory instance
    at javax.el.ExpressionFactory.newInstance(ExpressionFactory.java:225)
    at javax.el.ExpressionFactory.newInstance(ExpressionFactory.java:183)
    at javax.el.ExpressionFactory.newInstance(ExpressionFactory.java:89)
    at org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm.<clinit>(InterpolationTerm.java:60)
    ... 68 more
Caused by: java.security.AccessControlException: access denied (java.io.FilePermission C:\Program Files\Java\jdk1.6.0_35\jre\lib\el.properties read)
    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:374)
    at java.security.AccessController.checkPermission(AccessController.java:546)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:532)
    at com.google.appengine.tools.development.DevAppServerFactory$CustomSecurityManager.checkPermission(DevAppServerFactory.java:283)
    at java.lang.SecurityManager.checkRead(SecurityManager.java:871)
    at java.io.File.exists(File.java:731)
    at de.odysseus.el.ExpressionFactoryImpl.loadDefaultProperties(ExpressionFactoryImpl.java:258)
    at de.odysseus.el.ExpressionFactoryImpl.loadProperties(ExpressionFactoryImpl.java:283)
    at de.odysseus.el.ExpressionFactoryImpl.<init>(ExpressionFactoryImpl.java:162)
    at de.odysseus.el.ExpressionFactoryImpl.<init>(ExpressionFactoryImpl.java:147)
   :   :
   :   :

el.propertiesとかいうファイルのことをいろいろ調べたものの、情報少なく、原因不明。

おそらく、de.odysseus.el.ExpressionFactoryImplがファイルを読み込もうとしている。で、GAEではファイルの読み書きは禁止されているのでエラーとなっている。
I think “juel-2.2.6” try to read el.properties. But, on GAE, it doesn’t permit to read files. So Error has occured. I don’t know Why “juel” try to read.

しかし、SpringのTutorialをみると、きちんと動いている!
But , according to “SpringTutorial”,  It’s work well !
Spring Security in Google App Engine | SpringSource Team Blog


(2)Success!!
最終的に分かったのは、どうもhibernate-validator-5.*からちょっとつくりが変わっているみたい。
I think that  hibernate-validator-5.* has changed any architecture.

hibernate-validator-4.3.0.Finalを使ったらうまく動くようになりました。
I try to use “hibernate-validator-4.3.0.Final”, it’s work well !!
Don’t use “hibernate-validator-5.*” on GAE !!  Use “hibernate-validator-4.3.0″

また、hibernate-validator-4.3.0を使った場合、FasterXML、juelは無くてもOKです。
You don’t need “FasterXML and juel”, when you use “hibernate-validator-4.3.0” on GAE.

Javaの使い方メモ(Interface versus Abstract Class)

たまたま読んだ記事が、ためになったのでざっくり訳してみることにした。
こちらの記事 → Java: Interface versus Abstract Class

<補足>~~~~~~~~
Interfaceの使い方とか、基本的なところはこちらのサイトがわかりやすかった

(参考) あぷり部屋 - Java入門講座(インターフェイスについて理解しよう!)
(参考) interfaceについて本気出して考えてみた – 都元ダイスケ IT-PRESS
(参考) Java: Are objects of the same type as the interface implemented?
~~~~~~~~~~~~

ここから先、翻訳内容です。


What’s the difference between an interface and an abstract class in Java?
interfaceと抽象クラスの違いは?

まずは、それぞれの定義をざっくり確認してみる。そのうえで違いを確認してみよう!

abstractなメソッドを持つクラスは、abstractと宣言しないといけない。abstractなメソッドというのは、「メソッドを定義するが、具体的な処理内容を記述しない」といった形のメソッド。

When to use abstract methods in Java?
抽象メソッドのつかいどころ。

「なんで抽象メソッドをつかいたいって思うか?」、ってことを以下のExampleで考えてみる。

/* the Figure class must be declared as abstract 
   because it contains an abstract method  */

public abstract class Figure
{
	/* because this is an abstract method the 
	   body will be blank  */
	public abstract float getArea();	
}

public class Circle extends Figure
{
	private float radius;

	public float getArea()
	{
		return (3.14 * (radius * 2)); 	
	}
}

public class Rectangle extends Figure
{
	private float length, width;

	public float getArea(Figure other)
	{
		return length * width;
	}
}

上記では、抽象メソッドgetArea()が定義されています。なので、Figureクラスは抽象メソッドを持っていることになり、abstractと宣言しないといけない。

Figureクラスを継承したCircleクラス、Rectangleクラスが定義されており、それぞれのクラスにはgetAreaメソッドが定義されています。

ここでのポイントは、「なぜ、Figureクラスの中でgetAreaメソッドを抽象メソッドとして定義したか?」ということ。getAreaメソッドでは、図形の面積を計算しています。

Figureクラスの中では、特定の図形を指定していません。そのため、Figureクラスの中ではgetAreaメソッドに具体的な計算内容を定義していません(定義できない)。つまり、これが、Figureクラスの中ではgetAreaメソッドを抽象クラスとして定義している理由ですね。

Figureクラスを継承する場合、以下の2パターンが考えられる。
・Figureクラスを継承したサブクラスでgetAreaメソッドを実装(オーバーライド)する。
・Figureクラスを継承したサブクラスでgetAreaメソッドを実装(オーバーライド)しない場合、そのサブクラスは抽象クラスとして宣言する。

A non abstract class is called a concrete class
抽象クラスでないものは、具象クラスという。

ということをふまえて、次に、「Interfaceって何か、interfaceと抽象クラスの違いは?」を考えてみる。

Java interface versus abstract class
Interface vs 抽象クラス

Interfaceなクラスではありません。ってところが、抽象クラスとの違いです。
Interfaceは、ほかのクラスに継承されて利用されるtype(型?)になります。

Interfaceを継承するクラスは、以下の2つの条件を満たす必要があります。
・クラスを定義するときに、”implements Interface_Name“といった形で、定義する必要があります。
・クラスの中で、Interfaceで定義されているすべてのメソッドを実装しておく必要があります。

以下のような”Dog”というInterfaceがあった場合に、

public interface Dog
{
    public boolean Barks();
    public boolean isGoldenRetriever();
}

このInterfaceを継承したクラスは、以下のような感じになる。

public class SomeClass implements Dog
{
    public boolean Barks{
    // method definition here
    }

    public boolean isGoldenRetriever{
    // method definition here
    }
}

ここまでの内容で、Interfaceと抽象クラスについて、基本的なことを理解できたと思います。というわけで、それぞれの違いについて見ていきたいと思います。

Abstract classes and inheritance
抽象クラスと継承

(1)抽象クラスは、ほかのクラスに継承されるものになります。あるクラスが継承されるということは、互いのクラスに強い関連性(依存関係?)が生じるということになります。

たとえば、抽象クラスとしてCanineクラスが定義されているとします。Canineクラスを継承したクラス(DogとかWolfクラス)は、Canineファミリーに所属すべきです。「すべき」ということは、開発者がメンテナンス・維持管理していく、ってことになる。

それに対しInterfaceの場合、Interface本体と、「InterfaceをImplementしたクラス」との関係は強くない。
たとえば、Houseというクラスがあったとします。Houseクラスは、AirConditioningというInterfaceをImplementしているとします。この場合、HouseにAirConditioning(空調設備)があるかどうかはそれほど強い関係性ではありません。

逆に「Houseクラスを継承したApartmentクラスと、Houseクラス」といった関係性のほうがはるかに強いつながりを持っています。
なぜかというと、ApartmentはHouseの中の1つに属していることから関連が強いわけです。そのため、クラスの継承をつかってきっちり定義するべきです。この点がInterfaceとの違いになってきます。

まとめると、
抽象クラスとそれを継承したクラスの間に、つよい関係性が考えられる、という場合、「抽象クラスをつかうべき」です。抽象クラスは、”継承”により、クラス間が密接につながります。つまり関係性が強くなる。
それに対し、Interfaceの場合、Interfaceと、それをImplementしたクラスの間に、つよい関係性は必要ありません。

Interfaces are a good substitute for multiple inheritance
Interfaceは、多重継承の代わりになる。

(2)Javaは多重継承を許可していません。Javaでは、クラスは1つのクラスしか継承できません。抽象クラスであろうがそうでなかろうが、継承できるのは1つだけです。
ですが、Interfaceは複数Implementできます。そのため、多重継承の代替になりえます。

抽象クラスとInterfaceの主な違いの1つとしては、「Javaのクラスは、抽象クラスを1つだけしか継承できないが、Interfaceは複数Implementできる」、と言えます。

Abstract classes can have some implementation code
抽象クラスは、実装コードを持てる。

(3)抽象クラスは、メソッドに処理手順を書き込めます。抽象クラスは、具体的な処理を記載したメソッドを持つことができます。また、抽象クラスは、コンストラクタ・インスタンス変数も持つことができます。
ですが、Interfaceはメソッドに処理手順を書き込めません。メソッドの名前だけ定義できます。InterfaceをImplementするクラスは、Interfaceで定義されているメソッドについて、すべて実装する必要があります。

When to use abstract class and interface in Java
抽象クラス、Interfaceのつかいどころ。

抽象クラス、Interfaceをつかうときのガイドラインを以下に示します。

1.次のように考えているなら、抽象クラスが適している。
「共通のBaseとなるクラスを用意して、それを継承したクラスを使う」といった形で、”継承”を使っていこう!

2.Publicな使い方をしたくないなら、抽象クラスをつかうのがよい。Interfaceで定義するメソッドはPublicでないといけない。

3.将来的にメソッドの追加が予想される場合、抽象クラスを使うほうがよい。Interfaceにメソッドを追加すると、そのInterfaceをImplementしているクラスすべてに、メソッド追加しないといけなくなる。おおさわぎになるかも!?

4.さしあたり、APIに変更が入らないという場合、Interfaceをつかうのがよい。

5.多重継承のようなことを行いたいと思っている場合、Interfaceを使うのがよい。複数のInterfaceをImplementすることができる。

Javaの使い方メモ(Downcast, Generics)

Javaの勉強中です。
Tutorialとか、Examplesにあるコードを見ていると、

String str = (String) list.get(0);
とか、
List<String> list = new ArrayList<String>();

といった記述を目にすることがあって、「この丸カッコ・三角カッコで囲まれているものは何だろう。」と思ったものの、「型を指定しているんだろうなぁ、きっと。」と考えてスルーしてきました。
が、いまさらながら、きちんと調べてみました。

使っている環境
Java JDK1.6.0_35(64bit版)


(1)DowncastとGenerics
Genericsの機能がJavaになかった頃、Listに文字列を追加して、それを取出す場合、このようにしていた。
(参考) Javaジェネリクス再入門 – プログラマーの脳みそ

List list = new ArrayList();
list.add("hello!");
String str = (String) list.get(0);

ArrayListに追加される要素は、Object型となっている。そのため、list.get(0)として、要素を取出したとき中身がStringであったとしても、要素の型はObject型となっている。

このままの状態だと、String型の変数strに値を格納することができない。そのため、
(String) list.get(0)

として、Downcastしている。(上位の型(Object型)を下位の型(String型)に変換している)
Downcastしたときの問題点として、「コンパイル時にエラーが出ないが、アプリ実行時にエラーが発生することがある」という問題がある。
上記の例の場合、listの要素にString以外のものが入っていた場合、「コンパイルは通るが実行時エラー」となる。ClassCastExceptionが発生するらしい。
このClassCastExceptionを解決するにはは、listにどんなデータが格納されていたかを調査する必要があり、デバッグがとても大変らしい。

そこで、現在ではJavaのGenerics機能をつかって、以下のように書いている。

List<String> list = new ArrayList<String>();
list.add("hello!");
String str = list.get(0);

List<String>と書くことで、listにString型がバインドされる。これにより、listに格納される要素はString型のみとなる。また、list.getするとString型が返るようになる。


(2)Genericsとは?
クラス定義でGenericsを使える。
(参考)ジェネリックスとは?なぜジェネリックスなのか – Java 入門

たとえば以下のようなクラスBoxを定義すると、

public class Box<T> {
    T o;

    public Box(T o){
        this.o = o;
    }
    public T get(){
        return o;
    }
}

クラスを使うときに、Box<Integer>、Box<String>といった形で指定することにより、それぞれの型用の Box クラスを用意できるようになる。以下のような感じでBox<Integer>を使うと、getしたときの返り値もInteger型になる。

Box<Integer> b = new Box<Integer>(new Integer(123));
Integer i = b.get();
System.out.println(i);

Genericsを使うことにより、Integer用・String用といったクラスを別々に定義する必要がなくなり、汎用化できる。僕に使いこなせるかどうかは、わからないけど(笑)


(3)もっとGenerics
メソッドにも使える。
(参考)Java Generics Tutorial

public class GenericMethodTest
{
   // generic method printArray                         
   public static < E > void printArray( E[] inputArray )
   {
      // Display array elements              
         for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }

    public static void main( String args[] )
    {
        // Create arrays of Integer, Double and Character
        Integer[] intArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };

        System.out.println( "Array integerArray contains:" );
        printArray( intArray  ); // pass an Integer array

        System.out.println( "\nArray doubleArray contains:" );
        printArray( doubleArray ); // pass a Double array

        System.out.println( "\nArray characterArray contains:" );
        printArray( charArray ); // pass a Character array
    } 
}

printArrayメソッドでは、渡された配列の内容を表示しています。Genericsをつかうことで、int型の配列・double型の配列・String型の配列に対応できるようになっています。
実行すると以下のようになる。

Array integerArray contains:
1 2 3 4 5 6

Array doubleArray contains:
1.1 2.2 3.3 4.4 

Array characterArray contains:
H E L L O

(4)Downcastの使い道
どうしてもDowncastを使う場合、ClassCastExceptionを避けるために、instanceof演算子などを使って型のチェックをしておくべき。
(参考)8章:継承
(参考)強く型付けされているJavaの理解に必修の“型変換” (2/3) – @IT

    if( obj instanceof String )
        str1 = (String)obj;

でも、「そもそもDowncastをどういうときに使うべきか?」っていう素朴な疑問がわいてくる。つかいどころ、ってどこ?
これについては、こちらのサイトに例がありました。
(参考)Downcasting in Java

オブジェクトとオブジェクトの型を比較したいときにDowncastを使うと便利らしい。

public class Person
{
  private String name;
  private int age;

  public boolean equals(Object anObject)
  {
     if (anObject == null)
         return false;

     /* The object being passed in is checked
         to see it's class type which is then compared
         to the class type of the current class.  If they
         are not equal then it returns false
     */

     else if (getClass( ) != anObject.getClass())
         return false;

     else
     {
        /* 
         this is a downcast since the Object class
         is always at the very top of the inheritance tree
         and all classes derive either directly or indirectly 
         from Object:
        */
        Person aPerson = (Person) anObject;
        return ( name.equals(aPerson.name) 
                && (age == aPerson.age));
     }
  }

}

上記の例では、Personクラスのequals()メソッドでDowncastを使っています。異なる2つのオブジェクト(自分自身とanObject)のnameとageを比較しています。

想定される使用イメージはこんな感じ。
・Personクラスを継承したクラスとして、Japanese, American, Chineseといったクラスがあるとします。
・で、それぞれのクラスを比較したい。

Personクラスのequals()メソッドでは、引数として様々なクラスを引き受ける必要があるため、引数としてObject型の変数anObjectを使っています。Personクラスと比較するときに、型を合わせるために、Downcastしています。

今回の例では、ClassCastExceptionを避けるために、instanceof演算子ではなくgetClassを使っています。なのでJapaneseどうし、Americanどうしであった場合に、Downcastして比較することになりますね。

なお、instanceof演算子とgetClassの微妙な違いが気になったので、以下に引用しておきます。

~~~~~~~~~~~~~~~~~~
(参考)4章 継承
instanceof演算子について(以下、引用)
<オブジェクト変数>に代入されているインスタンスのクラスが<クラス名>と一致すればtrueを返します。また、インスタンスが<クラス名>クラスを継承している場合も、trueが返ります。

ObjectクラスのgetClass()を用いてインスタンスobjがクラスAのインスタンスであるか調べる場合、obj.getClass() == A.classと比較することでできます。
もし、インスタンスobjがクラスAを継承したクラスA’のインスタンスである場合、上記の比較構文はfalseを返します。instanceofであればtrueになることに注意してください。
~~~~~~~~~~~~~~~~~~