通过Java实现你的twitter应用

关于Twitter这里就不多介绍了, 基本上能看到这篇文章的都用过的, 当然不知道也没关系, Google一下.由于Twitter的API 开放性, 所以其应用现在可以用多如牛毛也描述了, 各种各样的, 本文基于Java, 通过twitter API来获取twitter消息, 并以PDF的形式进行归档.准备工具:

1. HttpClient 3.x 库文件, 通过此库来从Twitter API获取消息, 同时需要 Comomons LoggingCodec 两个库文件. 请自行下载.

2. dom4j 库文件, 通过此库来解析Twitter的消息XML和获取中间的数据.

3. iText 库文件, 用来创建PDF文档.


每个Twitter应用都是基于twitter API来实现的, 有关Twitter API的使用可以看这里: Twitter API wiki(官方的,需要翻墙), 这里我们假设你只对获取数据感兴趣, 同时想以某种方式显示他们, 至于其他功能, 相信通过本教程的介绍, 你可以自己去钻研了.从TweetConsumer开始:

import org.dom4j.Element;
public interface TweetConsumer {
    public String tweet(Element element) throws TweetException;
}

Twitter API提供XML, JSON 和RSS几种格式, 这里我们选择XML格式, 通过dom4j来解析, 上面是一个叫做TweetConsumer的借口, 作用就是来处理消息的, 其实现在后面会讲到.

自定义一个消息异常, 当出现问题时抛出:

public class TweetException extends Exception {
    private static final long serialVersionUID = 7577136074623618615L;
    public TweetException(Exception e) {
        super(e);
    }
}

接下来是本文的重点, 通过Twitter API去获取消息:

public class TweetProducer {
    protected HttpClient client;
    protected TweetConsumer consumer;

    public void createClient(String username, String password) {
        client = new HttpClient();
        client.getState().setCredentials(
            //由于GFW的原因, 直连twitter肯定是不行了, 建议使用第三方API,比如我的GAE应用:xiao-quan.appspot.com
            new AuthScope("twitter.com", 80, AuthScope.ANY_REALM),
            new UsernamePasswordCredentials(username, password));
        client.getParams().setAuthenticationPreemptive(true);
    }

    public void setConsumer(TweetConsumer consumer) {
        this.consumer = consumer;
    }

    public String execute(String since_id) throws TweetException {
        String id = null;
        String tmp;
        //由于GFW的原因, 直连twitter肯定是不行了, 建议使用第三方API,比如我的GAE应用:http://xiao-quan.appspot.com/api/statuses/friends_timeline.xml
        GetMethod get = new GetMethod("http://twitter.com/statuses/friends_timeline.xml");
        if (since_id != null && since_id.length() > 0) {
            get.setQueryString("?count=200&since_id=" + since_id);
        }
        else {
            get.setQueryString("?count=200");
        }
        get.setDoAuthentication(true);
        try {
            client.executeMethod(get);
            SAXReader reader = new SAXReader();
            Document document = reader.read(get.getResponseBodyAsStream());
            Element root = document.getRootElement();
            for (Iterator i = root.elementIterator(); i.hasNext(); ) {
                tmp = consumer.tweet((Element)i.next());
                if (id == null) id = tmp;
            }
        } catch (HttpException e) {
            throw new TweetException(e);
        } catch (IOException e) {
            throw new TweetException(e);
        } catch (DocumentException e) {
            throw new TweetException(e);
        } finally {
            get.releaseConnection();
        }
        return id;
    }
}

TweetProducer 里面有三个方法:
createClient(), 创建HttpClient对象, 通过HttpClient来连接Twitter API, 由于Twitter使用了HTTP Basic Authentication,
所以你需要提供你的twitter用户名和密码来登录, 获取你的朋友们的消息.
setConsumer(), 传一个TweetConsumer接口给TweetProducer, 我们有多种输出方案, 当然这里要通用点.
excute(), 这是才是做事情的地方, 获取哪个消息ID以后的消息, 返回一个消息ID.

获取消息:

GetMethod get = new GetMethod("http://twitter.com/statuses/friends_timeline.xml");

Twitter API默认获取的消息条数是20, 最大200, 你可以自己定义获取的数量, 同时也可以通过一些参数来指定获取第几页的消息等, 具体使用参考Twitter API WIKI上的定义.

例如, 获取消息ID为since_id以后的200个消息:

get.setQueryString("?count=200&since_id=" + since_id);

设置完查询条件后, 执行:

client.executeMethod(get);

将返回的信息传给SAXReader,以便获取消息root:

SAXReader reader = new SAXReader();
Document document = reader.read(get.getResponseBodyAsStream());
Element root = document.getRootElement();

最后迭代每个带 标签的Element:

for (Iterator i = root.elementIterator(); i.hasNext(); ) {
    tmp = consumer.tweet((Element)i.next());
    if (id == null) id = tmp;
}

由于Twitter的消息列表是按时间降序排列, 那么第一个消息就是最新的消息了, 这个消息ID就是excute()要返回的String.

获取到消息后, 接下来我们就输出消息:

import org.dom4j.Element;

public class SystemOutTweets implements TweetConsumer {
    public String tweet(Element tweet) {
        System.out.println(tweet.asXML());
        return tweet.elementText("id");
    }

    public static void main(String[] args) throws TweetException {
        TweetProducer t = new TweetProducer();
        t.createClient("gavinquan", "123456");
        t.setConsumer(new SystemOutTweets());
        System.out.println("Start tweeting:");
        String id = t.execute(null);
        System.out.println("The End; last tweet: " + id);
    }
}

将上面的介绍的代码整理好, 并执行, 就会在控制台输出, XML格式的消息列表, 如下是其中的一个片段:

<status>
  <created_at>Wed Sep 09 06:07:04 +0000 2009</created_at>
  <id>3858278078</id>
  <text>gmail服务器错误了?</text>
  <source><a href="http://twitterfox.net/" rel="nofollow">TwitterFox</a></source>
  <truncated>false</truncated>
  <in_reply_to_status_id/>
  <in_reply_to_user_id/>
  <favorited>false</favorited>
  <in_reply_to_screen_name/>
  <user>
    <id>10415752</id>
    <name>hugege</name>
    <screen_name>hugege</screen_name>
    <location>Shantou Guangdong China </location>
    <description>www.gegehost.com 博客主机服务</description>
    <profile_image_url>http://a1.twimg.com/profile_images/329549706/hugegelogo_normal.jpg</profile_image_url>
    <url>http://hugege.com/</url>
    <protected>false</protected>
    <followers_count>1607</followers_count>
    <profile_background_color>C6E2EE</profile_background_color>
    <profile_text_color>663B12</profile_text_color>
    <profile_link_color>1F98C7</profile_link_color>
    <profile_sidebar_fill_color>DAECF4</profile_sidebar_fill_color>
    <profile_sidebar_border_color>C6E2EE</profile_sidebar_border_color>
    <friends_count>1335</friends_count>
    <created_at>Tue Nov 20 16:00:58 +0000 2007</created_at>
    <favourites_count>3</favourites_count>
    <utc_offset>28800</utc_offset>
    <time_zone>Beijing</time_zone>
    <profile_background_image_url>http://s.twimg.com/a/1252448032/images/themes/theme2/bg.gif</profile_background_image_url>
    <profile_background_tile>false</profile_background_tile>
    <statuses_count>1540</statuses_count>
    <notifications>false</notifications>
    <verified>false</verified>
    <following>false</following>
  </user>
</status>

同时会输出, 你的最后一个消息的消息ID:

The End; last tweet: 3858279409

当然你也可以直接通过这个ID, 来去获取你的该ID以后的消息:

t.execute("3858279409");

OK, 到这里, 我们的任务已经过去一半多了, 获取twitter消息, 那么接下来的内容, 我们来看如何将消息以PDF的形式进行归档.

通过iText生成PDF, 需要5步, 请看下面的Demo :

import java.io.FileOutputStream;
import com.lowagie.text.Document;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfWriter;

public class HelloWorld {
    public static void main(String args[]) {
        try {
            // step 1: create a Document object
            Document document = new Document();
            // step 2: connect the Document with an OutputStream using a PdfWriter
            PdfWriter.getInstance(document, new FileOutputStream("hello.pdf"));
            // step 3: open the document

            document.open();
            // step 4: add content
            document.add(new Paragraph("Hello World"));
            // step 5: close the document
            document.close();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

大体工作流程就如上面所说,相信都可以看懂, OK, 下面我就直接给出代码, 重点地方提一下.

PDFWriter类

import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.PageSize;
import com.lowagie.text.Phrase;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfWriter;
import com.quanlei.exception.TweetException;
import com.quanlei.twitter.TweetConsumer;
import com.quanlei.twitter.TweetProducer;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class PdfTwitter implements TweetConsumer {

    private int counter = 0;
    private Document document;
    private Font small_white = new Font(Font.HELVETICA, 8, Font.BOLD);
    private Font bold = new Font(Font.HELVETICA, 11, Font.BOLD);
    private BaseFont bfChinese = BaseFont.createFont("STSong-Light",
            "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
    private Font FontChinese = new Font(bfChinese, 12, Font.NORMAL);
    private SimpleDateFormat simpleDateTimeFormat =
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public PdfTwitter() throws DocumentException, IOException {
        document = new Document(PageSize.A4, 72, 36, 36, 36);

        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(
                "world.pdf"));
        writer.setPageEvent(new TwitterPage());
        document.open();
    }

    public void close() {
        document.close();
    }

    public String tweet(org.dom4j.Element tweet) throws TweetException {
        try {
            org.dom4j.Element user = tweet.element("user");
            PdfPTable table = new PdfPTable(new float[]{1, 10, 12});
            table.setTableEvent(new TableBackground());

            table.setWidthPercentage(100);
            table.setSpacingBefore(8);
            table.getDefaultCell().setPadding(5);
            table.getDefaultCell().setBorder(Rectangle.NO_BORDER);
            table.addCell("");
            table.addCell(new Phrase(user.elementText("screen_name"),
                    small_white));
            table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_RIGHT);

            table.addCell(new Phrase(simpleDateTimeFormat.format(
                    new Date(tweet.elementText("created_at"))),
                    small_white));
            table.getDefaultCell().setHorizontalAlignment(Element.ALIGN_LEFT);
            table.getDefaultCell().setFixedHeight(38);
            table.addCell(new Phrase(String.valueOf(++counter), small_white));
            table.getDefaultCell().setColspan(2);
            Phrase p = new Phrase();
            p.add(new Phrase(user.elementText("name"), FontChinese));
            p.add(new Phrase(": ", bold));
            p.add(new Phrase(tweet.elementText("text"), FontChinese));
            table.addCell(p);
            document.add(table);
            return tweet.elementText("id");
        } catch (DocumentException e) {
            e.printStackTrace();
            throw new TweetException(e);
        } catch (IOException e) {
            e.printStackTrace();
            throw new TweetException(e);
        }
    }

    public static void main(String[] args)
            throws DocumentException, org.dom4j.DocumentException,
            FileNotFoundException, IOException, TweetException {
        PdfTwitter pdf = new PdfTwitter();
        TweetProducer t = new TweetProducer();
        t.createClient("gavinquan", "123456");
        t.setConsumer(pdf);
        t.execute(null);
        pdf.close();
    }
}

上面的类已经可以输出PDF了, 但是不是很美观, 加点修饰, 每个Twitter消息一个框, 这是背景:

import java.io.IOException;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfPTable;
import com.lowagie.text.pdf.PdfPTableEvent;

public class TableBackground implements PdfPTableEvent {

    protected BaseFont bf;

    public TableBackground() throws DocumentException, IOException {
        bf = BaseFont.createFont(BaseFont.TIMES_BOLDITALIC, BaseFont.WINANSI,
                BaseFont.NOT_EMBEDDED);
    }

    public void tableLayout(PdfPTable table, float[][] width, float[] height,
            int headerRows, int rowStart, PdfContentByte[] canvas) {
        PdfContentByte cb = canvas[PdfPTable.BACKGROUNDCANVAS];
        cb.saveState();
        cb.setRGBColorFill(0x9a, 0xe4, 0xe8);
        cb.roundRectangle(width[0][0], height[2], width[0][3] - width[0][0],
                height[0] - height[2], 4);
        cb.roundRectangle(width[0][1], height[2] + 3,
                width[0][3] - width[0][1] - 3, height[1] - height[2] - 6, 4);
        cb.eoFill();
        cb.beginText();
        cb.setRGBColorStroke(0x9a, 0xe4, 0xe8);
        cb.setFontAndSize(bf, 28);
        cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_STROKE);
        cb.showTextAligned(Element.ALIGN_LEFT, "\"", width[0][1],
                height[1] - 10, 0);
        cb.endText();
        cb.restoreState();
    }
}

下面再画蛇添足, 加点外围修饰, 每个PDF页面左侧加上文字, 算是起点宣传作用:

import java.io.IOException;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfPageEventHelper;
import com.lowagie.text.pdf.PdfWriter;

public class TwitterPage extends PdfPageEventHelper {

    protected BaseFont bf;

    public TwitterPage() throws DocumentException, IOException {
        bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H",
                BaseFont.NOT_EMBEDDED);
    }

    @Override
    public void onEndPage(PdfWriter writer, Document document) {
        PdfContentByte cb = writer.getDirectContent();
        cb.saveState();
        cb.beginText();
        cb.setRGBColorStroke(0x9a, 0xe4, 0xe8);
        cb.setTextRenderingMode(PdfContentByte.TEXT_RENDER_MODE_STROKE);
        cb.setFontAndSize(bf, 18);
        cb.showTextAligned(Element.ALIGN_RIGHT, "www.quanlei.com", 68, 806, 90);
        cb.endText();
        cb.restoreState();
    }
}

最后配个截图, 为本文画上句号, 注, 由于图缩略了, 效果有点虚了:

PDF效果图

英文原版出处: http://www.javaworld.com/javaworld/jw-03-2009/jw-03-write-your-own-twitter-app.html , 本文抽取了原英文的重点, 并配以文字介绍,加工而成.

Android 系统初体验

这里介绍的Android系统体验是给那些没有Android手机的朋友提供的.

体验是通过LiveCD来达到的, 最近出现了一个Live-android的开源项目, 其目的就是让Android

系统可以通过LiveCD在x86平台来运行.

当然是用起来和真机的体验式有些区别, 不过基本上的一些功能还是可以用的.

下载地址:http://code.google.com/p/live-android/

最新版本是 Version 0.3, 分两个文件, 下载后需要进行文件合并, 分Windows 和Linux:

    然后通过Sun VirtualBox创建一个Linux的虚拟系统, 启动加载liveandroidv0.3.iso即可. 如下图:

android_1

android_2

还有一种体验方案, 提供开发人员用的, 那就是通过Eclipse+ADT(Android Development Tools)+ AVD(Android Virtual Device)来体验, 也是目前Android的开发套件. 关于具体配置方法, 网上很多, 大家可以自己找找, 大体功能和LiveCD类似, 只不过懂Java的可以跑跑自己设计的程序.

再遇 java.util.ConcurrentModificationException

今天又一次遇到java.util.ConcurrentModificationException的异常, 对一个Collection / Map进行遍历或者迭代遍历, 并删除一些符合条件的值时容易出现.

很早的时候自己直接使用的是遍历, 基本上每次都会出现这个问题,后经朋友点拨,这里应该用迭代遍历,就不会出现ConcurrentModificationException,
果然很久没出现了,不过今天又一次遇到这个问题,并且是在迭代遍历的情况下,于是决定彻底解决此问题,不能再次出现了,好好找找原因,弄个明白, 发布在这里:

http://disney2002.javaeye.com/blog/430645

终于完成了JasperReport的教程

最近因为项目需要, 接触和使用到了JasperReport, 由于这类教程网上比较少, 于是自己就想把平时用的一些技巧和经验写份教程, 一方面帮助需要的网友, 另一方面给自己做个笔记.

今天忙里抽闲,终于写完了,发表在这里,欢迎批评指正:

http://disney2002.javaeye.com/blog/429385

Java使用JXL 操作Excel时的一个问题

今天在做问题导出到Excel时遇到这样一个问题:

由于问题是从网络传输的, 数量不能太多, 因此原计划是分批次取过来, 然后写进excel, 再取再写进去, 直到取完, 关闭流.

结果发现这个问题, 就是jxl 的write方法仅能写一次, 也就是当你写数据时, 不能多次调用write方法, 仅能一次性写进你所要的excel里面, 否则将只记录第一次写进去的数据, 后写的根本没写进去, 而且不抛异常.

没办法, 只能分批取问题, 然后放到本地内存, 直到取完, 然后一次性写进Excel里面.

不过这样又有一个隐藏的问题, 就是本地数据太多, 大量数据写Excel时出现outOfMemory异常.

Java SE 6 新特性: JMX 与系统管理

最近因项目需求开始接触JMX,之前听说过,但不清楚个所以然来,转载俩篇比较容易懂的文章,文章有点长,加上版权问题,就只转载地址了。

1、来自IBM developerWorks 网站:

Java SE 6 新特性: JMX 与系统管理

http://www.ibm.com/developerworks/cn/java/j-lo-jse63/

2、来自 中程在线 网站

JMX介绍

http://www.itisedu.com/phrase/200604261751455.html