2008年10月26日星期日

JDK 和 JRE 文件结构

全文译自:JDK and JRE File Structure (Windows)

本文简要地介绍一下 JDK 的目录和这些目录所包含的文件。需要注意的是,JRE 的文件结构与 JDK 中 jre 目录的结构是相同的。

用于开发的文件和目录
Development Files and Directories

这一节先来看一下在 Java 开发中最为重要的文件和必需的目录。需要注意的,诸如 demos、Java 源代码和 C 语言头文件这些目录并不是必需的。关于这些目录结构会在 其他的文件和目录 一节中描述。

            jdk1.6.0
      _________|_______________________
     |         |                       |
    bin       lib                     jre
     |         |       ________________|_________________
 java.exe  tools.jar  |                                  |
 javac.exe   dt.jar  bin                                lib
 javap.exe         ___|___ _______         ________ _____|_______ _____
 javah.exe        |       |       |       |        |     |       |     |
 javadoc.exe  java.exe  client  server  rt.jar    ext security applet fonts
              java.dll    |       |   charsets.jar |
              awt.dll  jvm.dll jvm.dll        localedata.jar

假设 JDK 安装在了 c:\jdk1.6.0 目录中,下面是一些重要的目录:

c:\jdk1.6.0
JDK 安装的根目录。包括 COPYRIGHT、LICENSE 和 README 文件。还包括了 src.zip,这是 Java 平台源代码的压缩包。
c:\jdk1.6.0\bin
Java 开发工具集(Java Development Kit)中用于开发工具的可执行文件。系统的 PATH 环境变量应该包含这个目录项。关于这些工具,在 JDK Tools(JDK 工具)中可以了解更多的信息。
c:\jdk1.6.0\lib
开发工具所使用的文件。其中的 tools.jar 包含了在 JDK 中工具和实用工具支持的非核心类库。还有 dt.jar,这是 BeanInfo 文件设计时(DesignTime)的压缩包,用于告知交互式开发环境(interactive development environemts, IDE's)如何展示 Java 组件,并且如何让开发者自定义这些用于应用程序中。
c:\jdk1.6.0\jre
JDK 开发工具所使用的 Java 运行环境的根目录。这个运行环境是一个 Java 平台的实现。这个目录使用 java.home 系统属性来表示。
c:\jdk1.6.0\jre\bin
Java 平台所使用工具和类库的可执行文件和 DLL 文件。可执行文件与 c:\jdk1.6.0\bin 中的文件是相同的。这个 Java 载入工具服务作为一个应用程序加载器(用于替换在 JDK 1.1 版本发布旧的 jre 工具)。这个目录没有必要放在系统的 PATH 环境变量中。
c:\jdk1.6.0\jre\bin\client
包括用于 Java HotSpotTM Client 虚拟机的 DLL 文件。
c:\jdk1.6.0\jre\bin\server
包括用于 Java HotSpotTM Server 虚拟机的 DLL 文件。
c:\jdk1.6.0\jre\lib
Java 运行环境所使用的核心类库、属性设置和资源文件。例如:
  • rt.jar —— 引导类(运行时(RunTime)的类,包含了 Java 平台的核心 API)。
  • charsets.jar —— 字符转换类。
除了 ext 子目录(下面有描述)外,还有若干个其他的资源目录没有描述。
c:\jdk1.6.0\jre\lib\ext
用于 Extensions(扩展)Java 平台默认的安装目录。
  • localedata.jar —— 用于 java.text 和 java.util 包的区域(locale)数据。
c:\jdk1.6.0\jre\lib\security
包含了用于安全管理的文件。其中包括安全策略(java.policy)和安全属性(java.security)文件。
c:\jdk1.6.0\jre\lib\applet
用于 Applet 支持类的 jar 文件,可以放在 lib/applet 目录中。Applet 类通过 Applet 类加载器从本地文件系统中预加载,这样为减小大型 Applet 的启动时间提供了一些方式,好像从网上已经下载完成了一样。
c:\jdk1.6.0\jre\lib\fonts
包括平台使用的 TrueType 字体文件。
其他的文件和目录
Additional Files and Directories

这一节来看一下 demos、Java 源代码和 C 语言头文件的目录结构。

                           jdk1.6.0
                   _________|__________
                  |         |          |
                demo     include    src.zip
             _____|____ __________ __________
            |          |          |          |
         applets      jfc        jpda      plugin

上面所展示额外的目录有:

c:\jdk1.6.0\src.zip
包含 Java 平台源代码的压缩包。
c:\jdk1.6.0\demo
附有源代码的示例程序,用于展示如何在 Java 平台上编制程序。
c:\jdk1.6.0\demo\applets
能在 Web 页面上使用的 Applet。
c:\jdk1.6.0\demo\jfc
使用 Java 2DTM 和 JFC/Swing 功能的示例程序。
c:\jdk1.6.0\demo\jpda
使用 Java Platform Debugging Architecture(Java 平台调试架构)的示例程序。包括用于 javadd 和 jdb 实用工具的源代码。
c:\jdk1.6.0\demo\plugin
包含使用 Java 插件产品的演示。
c:\jdk1.6.0\include
C 语言的头文件,用于支持 Java Native Interface(Java 本地方法接口)Java Virtual Machine Debugger Interface(Java 虚拟机调试器接口)的本地代码程序设计

2008年10月22日星期三

Java Web 开发中的分页

本文先大致地介绍一下各种分页的方式,以及他们的优缺点。最后使用 JSP + Servlet + JDBC + Oracle/MySQL 的模式实现了一个分页查询的页面。

在 Web 开发中查询是个很常见的操作,但是查询所返回的结果集有时可能非常大,比如:Google 的搜索结果,有成千上万,数十万,数百万的搜索结果。对于这些庞大的结果集来说,不可能也不现实在一页中显示到客户端的浏览器上。这时我们就得采取分页措施,比如说每页显示 20 条记录,再用一些导向标记转向下一页或者让用户输入指定的页数,这样一来,数据传输量大大地减小了,速度也加快了,客户端能获得更好的体验。

分页方案

下面来看一下,在开发中常用的分页方法和分页技术有哪些?并且具有什么优缺点。在 POJOs in Action 一书中对此作了比较深入的分析(详见 Chris Richardson, POJOs in Action, Manning, 2006, Chapter 11: Implementing Dynamic Paged Queries),这里把该章中表 11.1 的分页方案引用一下,看看具体的分页方案有哪些。

分页方案 优  点 缺  点 适用场合
使用一条查询语句获取所有的数据 读取的一致性
只需要一条代价昂贵的查询语句
存储空间耗费
执行时间耗费
结果集小
查询主键 只需要一条代价昂贵的查询语句和多条代价小的查询语句 读取的不致性
存储主键空间上的开销
查询时间耗费
适量的结果集
JDBCResultSet的延时遍历 只需要执行一条查询语句
避免一次装入所有的数据
严重地限制了应用的扩展性
长事务将会锁住很多的数据
串行级别的长事务可能会失败
少量的用户
重新查询数据库 无状态
仅查出所需要显示的数据
重复查询
读取的不致性
结果集大
用户数多

来简单地看一下这四种分页方式的实现原理是什么,以及在什么情况下运用,详见的介绍参见 POJOs in Action 一书。下面这些介绍均节选自该书。

使用一条查询语句获取所有的数据

该方法是针对小数据量而首选的方法,该方法执行一条查询语句将所有的结果集存放在服务器或者浏览器的 Session 状态中。

这个方法只有在结果集很小的情况下才能使用。如果返回的结果集很大,那么执行这样的查询代价就非常之高了。把大量的结果放在服务器内存中,或者传到客户端的浏览器中,这种做法不仅没效率,而且也是不现实的。结果集会占用大量的内存和长时间地用于网络传输,因此这个方法很少用到。

查询主键

这是将前一种方法改动后所衍生出来的一种方式。应用首次执行一条 SQL 语句选出所有满足查询条件的数据记录的主键,存放到 Session 状态中。之后应用就可以通过这些主键,再发出一条查询语句以取出每一页的数据。

这个方法相对于上一种方法而言所占用的内存要少了很多。但是如果所得到的结果是非常大的话,即使只存放一些主键也会导致 Session 状态变得非常大,需要占用服务器大量的内存,并且很长时间才能发送到客户端中。而且这种方式相对于前者来说,这种方式在翻页的时候数据库中的记录更新过了,就会造成读取不一致。这种方式只适合于中等大小的结果集分页。

JDBCResultSet的延时遍历

这种方式与前两种一次装入所有数据的方法相反,这是采用应用保持与数据库的连接,当用户进行翻页的时候,遍历相应的 JDBC 结果集。但是,这种做法会导致 J2EE 应用程序数据库连接这个宝贵的资源,因此这种方案不能支持大量的用户访问。而且,如果长时间进行查询的话可能会锁住大量的记录。由于存在这些限制,这种方法几乎是不现实的。

重新查询数据库

从前面三种方案中可以看出,在大部分的应用中结果集都很大,无法全部放到内存中去。而且,在用户请求之间保持数据库的连接不放也是不现实的。因此,得选择一个更具扩展性的方法,就是在用户进行翻页时,应用程序重新查询数据库。一般有几种不同的做法:

  • 一次查询返回一页的数据。每次用户点“前一页”或者“后一页”按钮时,应用程序就到数据库中查出所需要页数的数据。
  • 一次查询多页的数据,保存在应用程序的 Session 中,这种做法可以减少数据库访问的次数,也可以减少响应的时间。

这种方法有诸多的优点:

  • 在处理 HTTP 请求时才进行数据库连接,无需要一直持有这个连接,所以具有良好的扩展性。
  • 需要显示多少数据,就加载多少数据,只需要加载一部分的数据,而无须加载整个结果集,这样占用的内存小,而且也加快了网络的传输速度。
  • 每一个查询只获取一页的数据,因此也减少了数据库的负担。
  • 对于大量的结果集来说,用户也许只需要看一两页的数据,相对于前面的几种方案来说,这种方案最为经济。

当然了,除了这些优点之外,它还存在着一些缺点,来看一下这种方式的缺点:

  • 在用户来回翻页的时候,可能会多次执行同样的查询语句,这会在一定程度上影响效率。
  • 用户看到的数据与实际的数据可能不一致,因为在翻页查询的时候,数据库可能被改变。

虽然有上面的一些缺点,但是相对于其优点来说已经是很不错的分页方案了,这个方案仍然是很多应用程序的最佳选择。

下面就将以这种分页方法通过一个 Java Web 应用程序的分页实例,来说明一下这种方案在具体应用上应该注意哪一些问题。这个分页采用最为基本的 JSP + Servlet + JDBC + MySQL/Oracle 来实现。

分页实例及注意事项

为了能更为方便地操作分页查询出来的数据,设计了一个用于封装分页类Pagination这个类用于封装一些分页数据和参数。分页数据就是当前页返回的结果集,分页参数由以下几个分页元数据构成:一页所显示数据的条目数量、当前页数、总的结果集数量(用于计算一共有多少页)、是否存在下一页。对于后两个参数来说适用于不同的场合,前者适用于需要知晓总页数的分页需求,后者适用于不需要知晓总页数的分页需求,只需要知道是否还存在下一页。这个类的代码如下:

import java.util.List;
import com.bao.config.Config;

public class Pagination {
   
    /**
     * 默认的当前页
     */
    private final static int DEFAULT_CURRENT_PAGE = 1;
    
    /**
     * 当前页
     */
    private int current;
    
    /**
     * 未分页时总的数据量
     */
    private int count;
    
    /**
     * 每页大小
     */
    private int pageSize;
    
    /**
     * 当前页的数据集
     */
    private List data;
    
    /**
     * 是否还存在下一页
     */
    private boolean hasNextPage;
    
    public Pagination(int current) {
        this.pageSize = Config.getConfig().getPageSize();
        this.current = current > 0 ? current : DEFAULT_CURRENT_PAGE;
    }
    
    /**
     * 根据总的数据量和每页大小计算总页数
     * @return      总页数
     */
    public int getTotalPage() {
        return (count + pageSize - 1) / pageSize;
    }

    /**
     * 获得当前页的数据数量
     * @return
     */
    public int getCurrentSize() {
        if(data == null) {
            return 0;
        }
        return data.size();
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {        
        this.count = count;
    }

    public int getCurrent() {
        return current;
    }

    public List getData() {
        return data;
    }
    
    /**
     * 设置数据集
     * @param data
     */
    public void setData(List data) {
        // 判断是否还有下一页
        if(data.size() > pageSize) {
            // 存在下一页
            setHasNextPage(true);
            // 移除多余的最后一条记录
            data.remove(pageSize);
        } else {
            // 不存在下一页
            setHasNextPage(false);
        }
        this.data = data;
    }
    
    public boolean isHasNextPage() {
        return hasNextPage;
    }
    public int getPageSize() {
        return pageSize;
    }
    
    /**
     * 在查询时多查一条记录,用于判断是否还存在下一页
     * @return
     */
    public int getQueryPageSize() {
        return pageSize + 1;
    }

    private void setHasNextPage(boolean hasNextPage) {
        this.hasNextPage = hasNextPage;
    }
}

(未完待续)

2008年10月13日星期一

表格拖影效果的 JavaScript

在很多地方看到鼠标从某个表格上划过时,表格行的背景色会逐渐地变淡直至消失,感觉非常酷。当时看到了也想做一个,可惜我始终不明白是怎么弄的,后来才知道是生成一定数量的渐变色,再使用 JavaScript 的计时器函数,逐一调用,呵呵。

这里主要涉及一些 RGB 渐变的东西,我是这样处理的:将终止颜色的 RGB 各值与起始颜色的 RGB 各值相减,再除以渐变步长,然后再在起始颜色的基本上将各分量逐步加上该步长。

用这种方法做一个 ColorGradient 的类用于产生渐变色的数组。

/**
 * 色彩渐变
 * @param start   起始颜色,使用 # 颜色表
 * @param end     终止颜色
 * @param step    渐变步长,大于 1 并且小于 200,默认为 16
 */
var ColorGradient = function(start, end, step) {
  this.start = this._toRGB(start);
  this.end = this._toRGB(end);
  this.step = (function() {
      if(!step || step < 1 || step > 200) {
        return 16;
      }
      return step;
    })();
  this.gradient = [];
}

ColorGradient.prototype = {

  // 获得渐变的颜色数组,以 6 位十六进制字符串返回
  getGradient : function() {
    return this._getGradient();
  },

  // 获得所设定的步长值
  getStep : function() {
    return this.step;
  },

  // 测试工作,用于在页面上渐变测试
  test : function() {
    var grad = this._getGradient();
    var n = 400 / this.step;
    var p = document.createElement('div');
    for(var i = 0; i < grad.length; i++) {
      var d = document.createElement('div');
      d.className = 'grad';
      d.style.width = n + 'px';
      d.style.backgroundColor = '#' + grad[i];
      p.appendChild(d);
    }
    document.body.appendChild(p);
  },

  // 生成渐变色数组
  _getGradient : function() {
    if(this.gradient.length > 0) {
      // 如果渐变色变量中有值直接返回
      return this.gradienth;
    }
    // 生成各步的颜色
    for(var i = 0; i <= this.step; i++) {
      var color = this._getNewColor(i).toString(16);
      while(color.length < 6) {
        color = '0' + color;
      }
      this.gradient.push(color);
    }
    return this.gradient;
  },

  // 根据 RGB 变化量获得每步的 RGB 颜色
  _getNewColor : function(k) {
    var rgbStep = this._getStepRgb();
    var rgb = 0;
    for(var i = 0; i < 3; i++) {
      var c = Math.floor(this.start[i] + rgbStep[i] * k);
      rgb |= c << ((3 - i - 1) * 8);
    }
    return rgb;
  },

  // 获得 RGB 三种颜色的变化量
  _getStepRgb : function() {
    var rgb = [];
    // 依次获得 R、G、B 的变化步长
    for(var i = 0; i < 3; i++) {
      rgb.push(this._getStepVar(i));
    }
    return rgb;
  },

  // 根据渐变步长、起始、终止颜色获得步长的变化量
  _getStepVar : function(index) {
    return (this.end[index] - this.start[index]) / this.step;
  },

  /**
   * 将 # 颜色转换成 RGB 颜色数组
   * 索引 0 -- 红色 R
   * 索引 1 -- 绿色 G
   * 索引 2 -- 蓝色 B
   */
  _toRGB : function(color) {
    var v = color.substring(1);
    var c = parseInt(v, 16);
    var rgb = [];
    for(var i = 0; i < 3; i++) {
      rgb.push(c >> ((3 - i - 1) * 8) & 0xff);
    }
    return rgb;
  }
}

上面这个ColorGradient类在生成对象后,调用getGradient()方法就能获得渐变颜色数组,从开始色到终止色。

为了这完成这个程序,还定义了几个工具类,特别是事件处理类。由于浏览器存在差异,各种浏览器中的事件绑定都不一样,比如 IE 中使用attachEvent函数,而 Firefox 中则使用addEventListener这个函数,为了避免这些差异就很有必要构造一些兼容各浏览器的工具方法。

还有一个小问题,在进行事件绑定时,所绑定的事件处理程序只是一个引用,并不能放上参数。比如有handler(str)方法,要将其绑定在objonclick事件上,如果使用obj.onclick = handler;的话,参数没办法传递,如果使用obj.onclick = handler(str);也不对,这样是把hadnler执行后的结果给了事件,如果用obj.onclick = 'handler(str)';这样也是不对的,这样只是把字符串给了事件,那该怎么办呢?

实际上可以通过调用一个方法,这个方法调用后将返回另一个方法引用,这样就能达到我们的目的了。

/**
 * 事件处理工具类
 */
var Event = function(){}

Event = {

  /**
   * 为 element 使用 handler 处理程序添加至 event 事件
   * 兼容 IE 及 Firefox 等浏览器
   *
   * 例如为 botton 对象添加 onclick 事件,使用 clickEvent
   * 方法作为处理程序:
   *   Event.addEvent(botton, 'click', clickEvent);
   *
   * @param element  需要添加事件的对象(Object)
   * @param event    需要添加的事件名称(String),不加“on”
   * @param handler  需要添加的方法引用(Function)
   */
  addEvent : function(element, event, handler) {
    if(element.attachEvent) {
      element.attachEvent('on' + event, handler);
    } else if (element.addEventListener) {
      element.addEventListener(event, handler, false);
    } else {
      element['on' + event] = handler;
    }
  },

  /**
   * 添加事件处理程序时,只能添加一个方法的引用,并不能给
   * 方法加上参数。比如定义了 clickEvent(str) 这个方法,现
   * 在要将其作为 obj 的 onclick 的事件处理程序,就可以用:
   * obj.onclick = Event.getFuntion(null, clickEvent, str);
   */
  getFunction : function(obj, fun) {
    var args = [];
    obj = obj || window;
    for(var i = 2; i < arguments.length; i++) {
      args.push(arguments[i]);
    }
    return function() {
        fun.apply(obj, args);
      };
  }
}

用于表格渐变的代码如下:

/**
 * 封装了拖影效果的类
 * @param id      表格的 id 名
 * @param time    各颜色渐变的时间间隔,默认为 10
 * @param start   起始颜色,默认为 #dcdcdc
 * @param end     结束颜色,默认为 #ffffff
 */
var Table = function(id, time, start, end) {

  // 获得所有的表格行
  this.rows = (function() {
      var tab = $(id);
      if(tab.tBodies.length == 0) {
        return tab.rows;
      }
      var rows = [];
      for(var i = 0; i < tab.tBodies.length; i++) {
        for(var j = 0; j < tab.tBodies[i].rows.length; j++) {
          rows.push(tab.tBodies[i].rows[j]);
        }
      }
      return rows;
    })();

  this.start = start || Table.DEFAULT_START;
  this.end   = end   || Table.DEFAULT_END;
  this.time  = time  || Table.DEFAULT_TIME;

  // 生成渐变色实例
  var color = new ColorGradient(this.start, this.end, 16);

  // 获得渐变色所有的颜色
  this.grad = color.getGradient();

  // 获得渐变的步长
  this.step = color.getStep();
}

// 默认的间隔时间
Table.DEFAULT_TIME = 10;

// 默认的起始颜色
Table.DEFAULT_START = '#dcdcdc';

// 默认的结束颜色
Table.DEFAULT_END = '#ffffff';

Table.prototype = {

  // 为各表格行添加事件
  addEvent : function() {
    for(var i = 0; i < this.rows.length; i++) {

      // 将所有的表格行的背景色设为结束色
      this.rows[i].style.backgroundColor = this.end;

      // 使用 this.overEvent 事件处理程序为表格行添加 onmouseover 事件
      Event.addEvent(this.rows[i], 'mouseover',
          Event.getFunction(this, this.overEvent, this.rows[i]));

      // 使用 this.outEvent 事件处理程序为表格行添加 onmouseout 事件
      Event.addEvent(this.rows[i], 'mouseout',
          Event.getFunction(this, this.outEvent, this.rows[i]));
    }
  },

  // 用于表格行 onmouseover 的事件处理
  overEvent : function(row) {
    row.style.backgroundColor = this.start;
    row.change = this.step;
  },

  // 用于表格行 onmouseout 的事件处理
  outEvent : function(row) {
    // 由于 outEvent 在执行时不属于 Table 这个类中,必须找个变量进行代理引用
    var me = this;
    if(row.change-- > 0) {
      // 逐渐更改背景色
      row.style.backgroundColor = '#' + me.grad[me.step - row.change];

      // 重复执行 outEvent
      setTimeout(function() {
          // 这里使用 me 的 Table 引用,由于 this 在这里所指的是 window 对象      
          me.outEvent(row);
        }, this.time);
    }
  },

  // 调试用代码
  _getRows : function(id) {
    var tab = $(id);
    alert(tab.tBodies.length);
  }
}

这里好像不能上传附件,完整的代码可以到 http://download.csdn.net/source/721339 上下载,如果没有 CSDN 账号或者是无法下载,请联系 frankiegao123@gmail.com

MySQL 忘记 root 密码时的处理

如果把 MySQL 的密码忘掉的话,不用太紧张。MySQL 的开发者们早就想到了出现这种情况的可能性。可以通过如下步骤进行处理:终止 MySQL 服务,然后使用 skip_grant_tables 重新启动它。这样做的结果是没有加载具有访问权限的数据表。这时可以删除经过加密的 root 密码,终止 MySQL 服务,然后不使用这个选项再一次启动它,这时 root 就没有密码了,可以重新给 root 一个新的密码。

在处理这种情况的时候,必须要有 MySQL 所运行其上的操作系统的系统管理员权限。

首先终止 MySQL 的运用。在 Windows 的服务管理器中结束 MySQL 的服务。

随后,在 MySQL 的配置文件[mysqld]部分中,输入选项skip_grant_tables。Windows 环境下的 MySQL 配置文件是 MySQL 的安装目录下的 my.ini 文件,而 Unix/Linux 下则是 my.cnf 文件。

然后,重新启动 MySQL 服务器,这时使用mysql来为主机名 localname 和实际的计算机名(仅在 Unix/Linux 下)重新设置密码:

root# mysql -u root
Welcome to MySQL monitor.
mysql> USE mysql;
Database changed.
mysql> UPDATE user SET password=PASSWORD('new password')
     > WHERE user='root' AND host='localhost';
Query OK, 1 row affected (0.00 sec)
mysql> UPDATE user SET password=PASSWORD('new password'wort')
     > WHERE user='root' AND host='computername';
Query OK, 1 row affected (0.00 sec)

在 mysql.user 数据表中做出这些改变之后,再一次如前面所说的那样停止 MySQL 服务。再从 MySQL 的配置中删除选项skip_grant_tables最后再次启动 MySQL 服务就可以了。

2008年10月11日星期六

测试

class Area {
   private String name;
   private int id;
   private int pid; 

   public Area(String name, int id, int pid) {
       this.name = name;
       this.id = id;
       this.pid = pid;
   }
   public int getId() {
       return id;
   }
   public void setId(int id) {
       this.id = id;
   }
   public String getName() {
       return name;
   }
   public void setName(String name) {
       this.name = name;
   }
   public int getPid() {
       return pid;
   }
   public void setPid(int pid) {
       this.pid = pid;
   }
}
package net.csdn.java.regex;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

import com.bao.util.io.FileUtil;

public class Test {

    public static void main(String[] args) throws IOException {
        String str = FileUtil.readFile2String("E:/myprog/eclipse33/csdn/src/net/csdn/java/regex/Test.java");
        JavaSyntaxHighlight java = new JavaSyntaxHighlight(str);
        System.out.println(java.process());
    }
}

/**
 * Java 语法高亮
 */
class JavaSyntaxHighlight {

    /**
     * 关键词
     */
    private static String keywords = "abstract assert boolean break byte case catch char class const " +
            "continue default do double else enum extends false final finally " +
            "float for if goto implements import instanceof int interface long " +
            "native new null package private protected public return short static " +
            "strictfp super switch synchronized this throw throws transient true try " +
            "void volatile while";
    
    /**
     * 多行注释
     */
    private final static String MULTI_COMMENT = "/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/";
    
    /**
     * 单行注释
     */
    private final static String SINGLE_COMMENT = "//[^\\r\\n]*+";
    
    /**
     * 字符串
     */
    private final static String QUOTE_STRING = "\"[^\\\\\"]*(?:\\\\.[^\\\\\"]*)*\"";
    
    /**
     * 字符
     */
    private final static String QUOTE_CHAR = "'[^\\\\']*(?:\\\\.[^\\\\']*)*'";
    
    /**
     * 高亮标签 
     */
    private final static String HIGHLIGHT_HTML_TAG = "";
    
    /**
     * 组合关键词表达式
     */
    private final static String KEYWORDS = "@interface|\\b(?:" + keywords.replace(' ', '|') + ")\\b";
    
    /**
     * 静态常量
     */
    private final static String STATIC_CONST = "(?<=\\b[A-Z_][A-Za-z0-9_]{0,50}\\.|[\\s({])[A-Z_][A-Z0-9_]*(?=\\s*[=+;,)}])";
    
    /**
     * Annotation
     */
    private final static String ANNOTATION = "@[A-Z_][A-Za-z0-9_]*\\b";
    
    private HighlightFormatter keywordFormatter         = HighlightFormatter.DEFAULT_KEYWORDS_CLASS;
    private HighlightFormatter multiCommentFormatter    = HighlightFormatter.DEFAULT_MULTICOMMENT_CLASS;
    private HighlightFormatter singleCommentFormatter   = HighlightFormatter.DEFAULT_SINGLECOMMENT_CLASS;
    private HighlightFormatter stringFormatter          = HighlightFormatter.DEFAULT_STRING_CLASS;
    private HighlightFormatter staticConstFormatter     = HighlightFormatter.DEFAULT_STATIC_CONST;
    private HighlightFormatter annotationFormatter      = HighlightFormatter.DEFAULT_ANNOTATION;
    
    private String text;
    
    /**
     * 匹配字符串的正则表达式
     */
    private final static Pattern REGEX_QUOTE_STRING = Pattern.compile(
            "(" + HIGHLIGHT_HTML_TAG + "|" + SINGLE_COMMENT + "|" + MULTI_COMMENT + ")|" +
            "(" + QUOTE_STRING + ")"    
        );
    
    /**
     * 匹配字符的正则表达式
     */    
    private final static Pattern REGEX_QUOTE_CHAR = Pattern.compile(
            "(" + HIGHLIGHT_HTML_TAG + "|" + SINGLE_COMMENT + "|" + MULTI_COMMENT + ")|" +
            "(" + QUOTE_CHAR + ")"    
        );
    
    /**
     * 匹配多行注释的正则表达式
     */
    private final static Pattern REGEX_MULTI_COMMENT = Pattern.compile(
            "(" + HIGHLIGHT_HTML_TAG + "|" + SINGLE_COMMENT + "|" + QUOTE_STRING + ")|" +
            "(" + MULTI_COMMENT + ")"    
        );
    
    /**
     * 匹配单行注释的正则表达式
     */
    private final static Pattern REGEX_SINGLE_COMMENT = Pattern.compile(
            "(" + HIGHLIGHT_HTML_TAG + "|" + MULTI_COMMENT + "|" + QUOTE_STRING + ")|" +
            "(" + SINGLE_COMMENT + ")"    
        );
    
    /**
     * 匹配 Java 关键词的正则表达式
     */
    private final static Pattern REGEX_KEYWORDS = Pattern.compile(
            "(" + HIGHLIGHT_HTML_TAG + "|" + MULTI_COMMENT + "|" + QUOTE_STRING + "|" + SINGLE_COMMENT + ")|" +
            "(" + KEYWORDS + ")"    
        );
    
    /**
     * 匹配静态常量的正则表达式
     */
    private final static Pattern REGEX_STATIC = Pattern.compile(
            "(" + HIGHLIGHT_HTML_TAG + "|" + MULTI_COMMENT + "|" + QUOTE_STRING + "|" + SINGLE_COMMENT + ")|" +
            "(" + STATIC_CONST + ")"
        );
    
    /**
     * 匹配 @ 注释的正则表达式
     */
    private final static Pattern REGEX_ANNOTATION = Pattern.compile(
            "(" + HIGHLIGHT_HTML_TAG + "|" + MULTI_COMMENT + "|" + QUOTE_STRING + "|" + SINGLE_COMMENT + ")|" +
            "(" + ANNOTATION + ")"
        );
    
    public JavaSyntaxHighlight(String text) {
        this.text = text;
    }
    
    public String process() {
        text = processHtmlEscape(text);        
        process(REGEX_QUOTE_STRING, stringFormatter);
        process(REGEX_KEYWORDS, keywordFormatter);
        process(REGEX_QUOTE_CHAR, stringFormatter);
        process(REGEX_MULTI_COMMENT, multiCommentFormatter);
        process(REGEX_SINGLE_COMMENT, singleCommentFormatter);
        process(REGEX_STATIC, staticConstFormatter);
        process(REGEX_ANNOTATION, annotationFormatter);
        return text;
    }    
    
    private void process(Pattern regex, HighlightFormatter formatter) {        
        Matcher matcher = regex.matcher(text);
        StringBuffer sb = new StringBuffer();
        while(matcher.find()) {
            if(matcher.start(1) > -1) {
                String str = matcher.group(1);
                str = processSpecialChar(str);
                matcher.appendReplacement(sb, str);
            } else if(matcher.start(2) > -1) {
                String str = matcher.group(2);
                str = processSpecialChar(str);
                matcher.appendReplacement(sb, formatter.getHtmlPrefix() + str + formatter.getHtmlSuffix());                
            }    
        }
        matcher.appendTail(sb);
        text = sb.toString();
    }
    
    private String processHtmlEscape(String str) {
        return str.replace("&", "&").replace("<", "<").replace(">", ">");
    }
    
    /**
     * 处理替换后的特殊字符
     * @param str
     * @return
     */    
    private String processSpecialChar(String str) {
        if(str.indexOf('\\') > -1) {
            str = str.replace("\\", "\\\\");
        }
        if(str.indexOf('$') > -1) {
            str = str.replace("$", "\\$");
        }
        return str;
    }
}

class HighlightFormatter {
    private String color;
    private boolean isBold;
    private boolean isItalic;
    private String font;
    private String size;
    
    private String styleClass;
    
    public final static String COLOR_DEFAULT = "#000000";       // default font
    public final static String FONT_DEFAULT = "Courier New";    // default color
    public final static String SIZE_DEFAULT = "12pt";           // default font size
    
    // highlight format of keywords
    public final static HighlightFormatter DEFAULT_KEYWORDS_CLASS       = new HighlightFormatter("keywords");
    public final static HighlightFormatter DEFAULT_MULTICOMMENT_CLASS   = new HighlightFormatter("multiComments");
    public final static HighlightFormatter DEFAULT_SINGLECOMMENT_CLASS  = new HighlightFormatter("singleComments");
    public final static HighlightFormatter DEFAULT_STRING_CLASS         = new HighlightFormatter("string");
    public final static HighlightFormatter DEFAULT_STATIC_CONST         = new HighlightFormatter("static");
    public final static HighlightFormatter DEFAULT_ANNOTATION           = new HighlightFormatter("annotation");
    
    /**
     * Using HTML style attribute
     * @param color
     * @param isBold
     * @param isItalic
     * @param font
     * @param size
     */
    public HighlightFormatter(String color, boolean isBold, boolean isItalic,
            String font, String size) {
        setColor(color);
        setBold(isBold);
        setItalic(isItalic);
        setFont(font);
        setSize(size);
    }
    
    /**
     * Using CSS class selector
     * @param styleClass
     */
    public HighlightFormatter(String styleClass) {
        this.styleClass = styleClass;
    }
    
    public HighlightFormatter() {
        
    }
    
    public String getHtmlPrefix() {
        StringBuilder sb = new StringBuilder();
        sb.append("<").append("span");
        if(StringUtils.isNotEmpty(styleClass)) {
            sb.append(" class=\"").append(styleClass).append("\">");
            return sb.toString();
        }
        sb.append(" style=\"");
        if(StringUtils.isNotEmpty(font) || isBold || isItalic || StringUtils.isNotEmpty(size)) {
            sb.append("font:");
            if(isItalic) {
                sb.append(" italic");
            }
            if(isBold) {
                sb.append(" bold");                
            }
            if(StringUtils.isNotEmpty(size)) {
                sb.append(" ").append(size);
            }
            if(StringUtils.isNotEmpty(font)) {
                sb.append(" ").append(font);
            }
            sb.append("; ");
        }
        if(StringUtils.isNotEmpty(color)) {
            sb.append("color: ").append(color).append("; ");
        }
        sb.append("\">");
        return sb.toString();
    }
    
    public String getHtmlSuffix() {
        return ""; 
    }
    
    private String processFont(String font) {
        String[] fonts = font.split(",\\s*");
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < fonts.length; i++) {
            if(i > 0) {
                sb.append(",");
            }
            if(fonts[i].indexOf(' ') > -1) {
                sb.append("\"").append(fonts[i]).append("\"");
            } else {
                sb.append(fonts[i]);
            }            
        }
        return sb.toString();
    }
    
    public String getColor() {
        return color;
    }
    public void setColor(String color) {
        this.color = color;
    }
    public boolean isBold() {
        return isBold;
    }
    public void setBold(boolean isBold) {
        this.isBold = isBold;
    }
    public boolean isItalic() {
        return isItalic;
    }
    public void setItalic(boolean isItalic) {
        this.isItalic = isItalic;
    }
    public String getFont() {
        return font;
    }
    public void setFont(String font) {
        this.font = processFont(font);
    }
    public String getSize() {
        return size;
    }
    public void setSize(String size) {
        this.size = size;
    }

    public String getStyleClass() {
        return styleClass;
    }

    public void setStyleClass(String styleClass) {
        this.styleClass = styleClass;
    }
}
测试 测试 测试测试