Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

2012-11-29

蛋痛的 netty UDP receiveBufferSize

netty 的 NioDatagramWorker 中分配了接收包缓存的最大大小,默认是 768Bytes,蛋痛的是第一次接收 768B后第二次缓存被扩展到 768B*2 buffer 中前768B是第一个包的前面部分数据,后768B是第二个包的前部分数据。
比较奇怪的是:
http://docs.jboss.org/netty/3.2/api/org/jboss/netty/channel/socket/DatagramChannelConfig.html
里面说了个 receiveBufferSize,但是设置后无效,经过多次尝试后发现必须要设置 receiveBufferSizePredictor 或者 receiveBufferSizePredictorFactory 才能够改变这个接收包缓存的大小。
如:

bootstrap.setOption("receiveBufferSize", 1048576);
bootstrap.setOption("receiveBufferSizePredictor", new FixedReceiveBufferSizePredictor(1048576));
// bootstrap.setOption("receiveBufferSizePredictorFactory", new FixedReceiveBufferSizePredictorFactory(1048576));

吐槽 netty 另外一个蛋痛的地方。在接收到包回写返回包时没有强制发送的方法,必须要等整个 messageReceived 方法返回以后才会把包写出去。如:
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
  ...
  e.getChannel().write(responsePacket); // not send out now
  Thread.sleep(30000L);
  ...
}
上面这块代码中 responsePacket 不是在回写的时候马上发出去的,而是要等 30秒方法结束后才发出去。
当然咯,这样也强制你在接收到包以后以新的线程来处理数据,但是至少在文档里也说清楚嘛。

//EOF Read More...

2012-02-09

效率,灵活,抽象,生产率

如果把我们的对编程语言的需求总结为四个:效率,灵活,抽象,生产率。那么,C语言玩的是前两个,而C++玩的是前三个,Java和C#玩的是后两个(抽象和生产率) http://www.infoq.com/cn/news/2012/02/2012-cpp-to-learn-or-not //EOF Read More...

2009-03-17

instance initializer

虽然用Java一段时间了,不过还是第一次听到 instance initializer (实例初始化块)
以前知道有一个 static initializer (静态初始化块)会在类加载的时候执行。
instance initializer 则是在构造函数执行过程中的某一时刻执行。见代码:


// LoadOrder.java

public class LoadOrder {
private Property _property = new Property();

{ // instance initializer
System.out.println("{ Load Block 1 }");
}

static { // static initializer
System.out.println("{ static 1 }");
}

public LoadOrder() {
System.out.println("LoadOrder()");
_property = new Property(this);
}

{
System.out.println("{ Load Block 2 }");
}

static {
System.out.println("{ static 2 }");
}

public static void main(String[] args) {
System.out.println("==== main() 1 ====");
new LoadOrder();
System.out.println("==== main() 2 ====");
new LoadOrder();
Property referee = new Property();
}
}

class Property {
public Property() {
System.out.println("Property()");
}
public Property(Object owner) {
System.out.println("Property(Object)");
}
}
// EOF

运行结果如下:

{ static 1 }
{ static 2 }
==== main() 1 ====
Property()
{ Load Block 1 }
{ Load Block 2 }
LoadOrder()
Property(Object)
==== main() 2 ====
Property()
{ Load Block 1 }
{ Load Block 2 }
LoadOrder()
Property(Object)
Property()

//EOF Read More...

2009-02-20

junit 4 使用

NOTE:
junit 4 中
1. 测试类不需要继承 TestCase,直接在方法前用 @Test 即可。
2. @BeforeClass, @AfterClass 方法需要是 public static 的。

//EOF Read More...

2008-12-13

DOM 还是 SAX

选择 DOM 还是选择 SAX,这取决于下面几个因素:

  • 应用程序的目的:如果打算对数据作出更改并将它输出为 XML,那么在大多数情况下,DOM 是适当的选择。并不是说使用 SAX 就不能更改数据,但是该过程要复杂得多,因为您必须对数据的一份拷贝而不是对数据本身作出更改。
  • 数据容量: 对于大型文件,SAX 是更好的选择。
  • 数据将如何使用:如果只有数据中的少量部分会被使用,那么使用 SAX 来将该部分数据提取到应用程序中可能更好。 另一方面,如果您知道自己以后会回头引用已处理过的大量信息,那么 SAX 也许不是恰当的选择。
  • 对速度的需要: SAX 实现通常要比 DOM 实现更快。
SAX 和 DOM 不是相互排斥的,记住这点很重要。您可以使用 DOM 来创建 SAX 事件流,也可以使用 SAX 来创建 DOM 树。事实上,用于创建 DOM 树的大多数解析器实际上都使用 SAX 来完成这个任务!

參考:
http://www.ibm.com/developerworks/cn/views/xml/tutorials.jsp?cv_doc_id=84979

//EOF Read More...

2008-12-10

struts 配置文件中元素的順序

今天才發現原來 struts 配置文件中元素之間的順序是有要求的。

controller 放在 message-resources 之前會報錯,雖然程序能夠正常運行,但在加載的時候會報一個“配置文件解析錯誤”。

看來順序還是不能變~

//EOF Read More...

2008-11-30

Linux 中使用NetBeans部署、运行Web应用要注意目录权限设置

使用的是 tomcat
要注意以下几个目录的权限要当前用户可写

$CATALINA_HOME/conf/Catalina/localhost/
$CATALINA_HOME/webapps/
$CATALINA_HOME/work/Catalina/localhost/
$CATALINA_HOME/temp/

否则,有可能会出现无法写入部署目录而部署失败:

Failed to deploy application at context path /xxx

另外,还要注意
$CATALINA_HOME/bin/
下几个.sh文件的当前用户执行权限,不然tomcat都无法运行。

//EOF Read More...

2008-09-26

FtpViewer 初始版本源代码

源代码下载:http://sites.google.com/site/dave3068/Home/my-project/FtpViewer_src.zip

见:http://sites.google.com/site/dave3068/Home/my-project

history:
2008-09-26 最初版本。
2008-09-27 有所改进,界面有所改进、增加删除功能。(今后不会再增加功能,有违做这个软件的初衷)

注:
为何不加入上传下载功能?
毕竟我写这个工具的初衷是:某FTP提供限额文件存放服务,而我的限额到了,我想看看哪些东西占用空间,并将其删除,并没有其它需求。
而对于FTP的上传、下载:每个操作系统安装后一般都默认自带了命令行的ftp工具,通过脚本可以很方便的实现文件的上传、下载。所以我认为没必要再提供多余的操作。


//EOF Read More...

sun.net.ftp.FtpClient 编码问题解决

使用 sun.net.ftp.FtpClient 在遇到中文文件名和中文目录名时会出现些问题。

  1. list() 得到的文件名乱码,这个很好解决:

    /**
    * 字符编码转换:ISO-8859-1 => GBK
    *
    * @param str
    * @return
    * @throws UnsupportedEncodingException
    */
    public static String iso2Gbk(String str)
    throws UnsupportedEncodingException {
    return new String(str.getBytes("ISO-8859-1"), "GBK");
    }


  2. 遇到某中文目录时会无法进入,这个问题困扰我好几天(这几天一直在摆弄这个)。
    发现服务端接收到的中文目录名都成乱码了,一开始我以为通过 new String() 的方式也可以解决。
    其实不然,怎么试都不行,好象在 sun.net.ftp.FtpClient 里面会自动转换成 ISO 的编码。
    今天偶然有机会看到 sun.net.ftp.FtpClient 的源码才发现果然如此。
    sun.net.ftp.FtpClient 有个超类 sun.net.NetworkClient 在里面会将所有传输出去的编码都转换成 ISO 的。看来问题出在这里了。
    同时 sun.net.NetworkClient 还有个:

    /* Name of encoding to use for output */
    protected static String encoding;

    这个可以控制其编码,于是乎:

    /**
    * 设置连接编码
    *
    * @param encodingStr
    */
    public static void setEncoding(String encodingStr) {
    sun.net.NetworkClient .encoding = encodingStr;
    }

    将编码设置成 GBK 问题即解决。


至此,遇到的两个编码问题都已解决,一切都是那么轻松 ^_^ 。

//EOF Read More...

2008-09-22

Java 获取FTP文件大小

使用 sun.net.ftp.FtpClient。


package h.davy;

import h.davy.ftp.FtpFileInfo;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import sun.net.TelnetInputStream;
import sun.net.ftp.FtpClient;

public class Test {

private static int test() {
FtpClient ftp = null;
try {
ftp = new FtpClient();
ftp.openServer("192.168.1.27");
ftp.login("t01", "12345");
// ftp.login("anonymous", "anonymous@do.c");

System.out.println(ftp.pwd()); // 当前目录
System.out.println(ftp.system());

System.out.println("===============================");
ftp.ascii();
// 获取目录文件列表详细信息
TelnetInputStream listStream = ftp.list();
byte[] buf = new byte[2048];
int n = 0;
while (true) {
if (0 > (n = listStream.read(buf, 0, buf.length)))
break;
System.out.println(n);
String fileName = new String(buf, 0, n);
System.out.println(fileName);
}
listStream.close(); // 需要这个关闭流

System.out.println("===============================");
getFilesInfo(ftp, "/");
System.out.println("===============================");
System.out.println(getFileSize(ftp, "/test.tar.bz2"));
System.out.println(getFileSize(ftp, "/phpMyAdmin/index.php"));
System.out.println("===============================");
getFilesInfo(ftp, "/phpMyAdmin");
// 退出登录
ftp.sendServer("QUIT\r\n");
} catch (IOException e) {
e.printStackTrace();
} finally {
// try {
// ftp.closeServer();
// } catch (IOException e) {
// e.printStackTrace();
// }
}
return -1;
}

/**
* 获取某文件大小
*
* @param ftp
* @param filePath
* @return
* @throws IOException
*/
static long getFileSize(FtpClient ftp, String filePath) throws IOException {
ftp.binary(); // 需要切换到 Binary 模式
ftp.sendServer(String.format("SIZE %s\r\n", filePath));
StringBuffer sb = new StringBuffer();
while (true) {
int c = ftp.serverInput.read();
char ch = (char) c;
if (c < 0 || ch == '\n') { // end of stream || end of line
StringTokenizer tk = new StringTokenizer(sb.toString());
int index = 0;
while (tk.hasMoreTokens()) {
String token = tk.nextToken();
if (index == 1) {
try {
return Long.parseLong(token);
} catch (NumberFormatException e) {
return -1L;
}
}
index++;
}
}
if (ch == '\n')
continue;
else if (c <= 0)
break;
sb.append(ch);
}
return -1L;
}

/**
* 获取某目录中的所有文件
*
* @param ftp
* @param path
* @return
* @throws IOException
*/
static List<FtpFileInfo> getFilesInfo(FtpClient ftp, String path)
throws IOException {
List<FtpFileInfo> ret = new ArrayList<FtpFileInfo>();

ftp.cd(path); // 切换到指定目录
ftp.ascii(); // 需要切换到 ASCII 模式
TelnetInputStream listStream = ftp.list();
StringBuffer sb = new StringBuffer();
while (true) {
int c = listStream.read();
char ch = (char) c;
if (c < 0 || ch == '\n') { // end of stream || end of line
FtpFileInfo ffi = new FtpFileInfo();
StringTokenizer tk = new StringTokenizer(sb.toString());
int index = 0;
while (tk.hasMoreTokens()) {
String token = tk.nextToken();
switch (index) {
case 0: // file attribute string
// 这里可以根据 attribute string 分析该文件是否为目录。
// (若为目录第一个字符为"d")
ffi.setAttributeStr(token);
break;
case 1: // ??
break;
case 2: // owner
break;
case 3: // group
break;
case 4: // file size
try {
ffi.setFileSize(Long.parseLong(token));
} catch (NumberFormatException e) {
e.printStackTrace();
}
break;
case 5: // month
case 6: // day
case 7: // time
break;
case 8: // file name
ffi.setFileName(token);
break;
}
index++;
}
ret.add(ffi);
sb.setLength(0);
}
if (ch == '\n') {
continue;
} else if (c <= 0)
break;
sb.append(ch);
}
listStream.close(); // 需要这个关闭流
return ret;
}

/**
* @param args
*/
public static void main(String[] args) {
System.out.println(test());
}

}


注:其中的 FtpFileInfo 仅仅是个人定义的一个 Java Bean 没什么实质的内容。

注:对于 ftp.list() 打开的流需要关闭,要不然后续读取操作会被挂起。


//EOF Read More...

2008-03-05

absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved

有一段时间没搞JSP了,今天用了标准标签库。奇怪,竟然会出现如下错误:

The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved

以前一直是正常的啊,探求了半天都未果。
(jstl.jar, standard.jar 我是放在WEB-INF/lib中的,以前这样做一直都OK啊!)
后来突发奇想,将这两个文件移到Tomcat的 shared/lib 中,然后重启那只笨猫,竟然OK了……
这个真的是诡异了,竟然在 WEB-INF/lib 中就找不到??
(其实WEB-INF/lib中我还有几个库也能找到啊!怎么就是标签库的就找不到呢?)

//EOF Read More...

2007-12-03

Java2D 3 or more Colors gradient paint (use TexturePaint)

Java中的 GradientPaint 画刷好像仅仅只能支持2色渐变,下面我用动态生成纹理画刷的方式变相实现了一个简单的多色渐变的效果:

package test.draw;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.TexturePaint;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.font.FontRenderContext;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;

import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * 彩虹纹理画刷
 *
 * @author h_Davy [dave3068 AT gmail DOT com]
 * @version 2007-11-28 上午02:09:46
 */
public class RainbowText extends JPanel
        implements Runnable, MouseListener {

    private static final long serialVersionUID = 1L;

    RainbowText() {
        super(true);
        init();
    }
    private void init() {
        setBackground(Color.white);
        setTextAttributes();
        //new Thread(this).start();
        addMouseListener(this);
    }


    private String text = "Rainbow Text";
    private Font f;
    private TextLayout tl;
    private AttributedCharacterIterator aci;
    //private FontMetrics fm;

    private void setTextAttributes() {
        f = new Font("serif",Font.BOLD,60);
        //fm = this.getFontMetrics(f);
        AttributedString as = new AttributedString(text);
        //as.addAttribute(TextAttribute.FONT, f, 0, text.length());
        AffineTransform at = new AffineTransform();
        //at.setToRotation(Math.toRadians(45));
        as.addAttribute(TextAttribute.FONT, f.deriveFont(at), 0, text.length());
        aci = as.getIterator();
        Color []c = new Color[]
            {Color.blue, Color.cyan, Color.green, Color.yellow,
                    Color.orange, Color.red, Color.magenta};
        float []w = new float[]
            {.09f, .15f, .16f, .17f, .15f, .16f};
        initTextImg(350, c, w, true);
    }




    public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;
        g2.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setStroke(new BasicStroke(2.f));

        // draw background
        g2.setColor(getBackground());
        g2.fillRect(0, 0, getWidth(), getHeight());
        g2.setColor(Color.blue);
        g2.drawRoundRect(5, 5, getWidth()-10, getHeight()-10, 10, 10);

        //
        g2.setStroke(new BasicStroke(1.5f));
        FontRenderContext frc = g2.getFontRenderContext();
        //tl = new TextLayout(text, f, frc);
        tl = new TextLayout(aci, frc);
        Rectangle2D rect = tl.getBounds();
        int x = getWidth()/2 - (int)rect.getWidth()/2,
            y = getHeight()/2 + (int)rect.getHeight()/2;
        g2.translate(x, y);
        //g2.rotate(Math.toRadians(-45));
        //g2.setColor(Color.yellow);
        //g2.fill(rect);
        g2.setPaint(new TexturePaint(bi, rect));
        g2.setComposite(AlphaComposite.SrcOver);
        //g2.rotate(Math.toRadians(30));
        tl.draw(g2, 0, 0);
        //AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f);
        //g2.setComposite(ac);
        W = (int)rect.getWidth();
        rect = new Rectangle2D.Float(X, 0, W, 1);
        g2.setPaint(new TexturePaint(bi, rect));
        R.setRect(-x+10, y, getWidth()-20, 20);
        g2.fillRect(R.x, 10, R.width, 20);
    }



    private boolean run = false;
    private int X = 0;
    private int W = 0;
    private static Rectangle R = new Rectangle();
    public void run() {
        try {
            while (run) {
                Thread.sleep(10);
                X+=1;
                if (X>=W) X = 0;
                //repaint();
                repaint(10, R.y+10, R.width, R.height);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void mouseClicked(MouseEvent e) {
        run = !run;
        new Thread(this).start();
    }



    // 纹理
    private BufferedImage bi;
    /**
     * 初始化纹理
     *
     * @param width 纹理总宽度
     * @param c 颜色值数组
     * @param w 各色带宽度所占百分比(w.length=c.length-1)
     * @param cyclic 是否循环
     */
    private void initTextImg(int width, Color []c, float w[], boolean cyclic) {
        bi = new BufferedImage(width, 1, BufferedImage.TYPE_4BYTE_ABGR);
        int n = c.length;
        int m = cyclic ? n : n - 1;
        // initialize widths
        float []ww = new float[m];
        float s = 0;
        int i;
        for (i = 0; i < m-1; i++) {
            s += w[i];
            if (s>=1) {
                break; // 以防输入的百分比过大时产生的异常
            } else {
                ww[i] = w[i];
            }
        }
        if (i<(m-1)) { // 若输入的百分比过大将剩下的均分
            float t = (1-s+w[i])/(m-i);
            for (; i<m; i++) {
                ww[i] = t;
            }
        } else { // 将剩下的分配给最后一个色块
            ww[m-1] = 1 - s;
        }
        // initialize colors
        int []rgbs = new int[n];
        for (i = 0; i < n; i++)
            rgbs[i] = c[i].getRGB();
        // the color's divides
        int a[] = new int[n];
        int r[] = new int[n];
        int g[] = new int[n];
        int b[] = new int[n];

        // initialize the color's divides
        for (i = 0; i < n; i++) {
            a[i] = (rgbs[i] >> 24) & 0xff;
            r[i] = (rgbs[i] >> 16) & 0xff;
            g[i] = (rgbs[i] >>  8) & 0xff;
            b[i] = (rgbs[i]      ) & 0xff;
        }

        // tail of color's divides (if you wana cyclic)
        int ta = ((rgbs[n-1] >> 24) & 0xff) - a[0];
        int tr = ((rgbs[n-1] >> 16) & 0xff) - r[0];
        int tg = ((rgbs[n-1] >>  8) & 0xff) - g[0];
        int tb = ((rgbs[n-1]      ) & 0xff) - b[0];

        // 用于存储平滑过渡的各颜色值
        int []interp = new int[cyclic ? n*256+1 : (n-1)*256+1];

        // 颜色线性平滑插值
        for (i = 0; i <= 256; i++) {
            float rel = i / 256.0f;
            int rgb;
            for (int j=0; j<n-1; j++) {
                rgb =
                    (((int) (a[j] + (a[j+1]-a[j]) * rel)) << 24) |
                    (((int) (r[j] + (r[j+1]-r[j]) * rel)) << 16) |
                    (((int) (g[j] + (g[j+1]-g[j]) * rel)) <<  8) |
                    (((int) (b[j] + (b[j+1]-b[j]) * rel))      );
                interp[i+256*j] = rgb;
            }
            if (cyclic) {
                rgb =
                    (((int) (a[0] + ta * rel)) << 24) |
                    (((int) (r[0] + tr * rel)) << 16) |
                    (((int) (g[0] + tg * rel)) <<  8) |
                    (((int) (b[0] + tb * rel))      );
                interp[n*256-i] = rgb;
            }
        }

        // fill the image
        /*float rt = (float)interp.length / width;
        for (i=0; i<width; i++) {
            bi.setRGB(i, 0, interp[(int)(i*rt)]);
        }*/
        int cur = 0;
        for (i=0; i<m; i++) {
            int wi = (int) (width*ww[i]);
            if (i==(m-1)) wi = width-cur;
            //System.out.print(wi+",");
            float rt = (float)256 / wi;
            for (int j=0; j<wi; j++) {
                bi.setRGB(cur+j, 0, interp[(int)(j*rt)+i*256]);
            }
            cur += wi;
        }
    }





    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
    }
    public void mousePressed(MouseEvent e) {
    }
    public void mouseReleased(MouseEvent e) {
    }


    /**
     * @param args
     */
    public static void main(String[] args) {
        JFrame f = new JFrame("");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(640, 480);
        f.setLocationRelativeTo(null);
        f.add(new RainbowText());
        f.setVisible(true);
    }

}


//EOF Read More...

Java2D Curves Control Point

大家刚接触Java图形编程的可能对 二次曲线、贝尔曲线 的几个控制点有点迷惑,我下面有段示例方便大家理解这两个曲线的几个控制点的作用:
package test.draw;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.GeneralPath;

import javax.swing.JFrame;
import javax.swing.JPanel;

/**
 * 方便理解Quadratic/Bezier曲线的几个控制点作用
 *
 * @author h_Davy [dave3068 AT gmail DOT com]
 * @version 2007-11-27 上午01:41:23
 */
public class CurvesTest extends JPanel
        implements MouseListener, MouseMotionListener, FocusListener {

    private static final long serialVersionUID = 1L;

    public CurvesTest() {
        super(true);
        init();
    }
    private void init() {
        setBackground(Color.white);
        setFocusable(true);
        addFocusListener(this);
        addMouseListener(this);
        addMouseMotionListener(this);
    }



    // 二次曲线
    private QuadCurve q = new QuadCurve(new Point(50, 100),
                new Point(200, 150), new Point(300, 100));
    // 贝尔曲线
    private BezierCurve b = new BezierCurve(
            new Point(100, 300), new Point(200, 400),
            new Point(300, 200), new Point(400, 300));


    public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;
        g2.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setStroke(new BasicStroke(2.f));

        // draw background
        g2.setColor(getBackground());
        g2.fillRect(0, 0, getWidth(), getHeight());
        g2.setColor(Color.blue);
        g2.drawRect(5, 5, getWidth()-10, getHeight()-10);

        // draw curves
        g2.draw(q.getPath());
        g2.draw(b.getPath());
        // fill path area
        g2.setColor(Color.lightGray);
        g2.fill(q.getPath());
        g2.fill(b.getPath());

        // paint drag handles
        g2.setColor(Color.orange);
        DragHandle []dhs = q.getDragHandles();
        for (int i = 0; i<dhs.length; i++)
            g2.fill(dhs[i]);
        dhs = b.getDragHandles();
        for (int i = 0; i<dhs.length; i++)
            g2.fill(dhs[i]);

    }

    private boolean isDrag = false; // 是否在拖动控制点
    private DragHandle curDrag = null; // 当前拖动中的控制点



    public void mousePressed(MouseEvent e) {
        // find out is mousePoint in any DragHandle
        DragHandle dh = q.findDragHandle(e.getPoint());
        if (dh != null) {
            curDrag = dh;
            isDrag = true;
            return;
        }
        dh = b.findDragHandle(e.getPoint());
        if (dh != null) {
            curDrag = dh;
            isDrag = true;
            return;
        }
    }
    public void mouseReleased(MouseEvent e) {
        isDrag = false;
    }
    public void mouseDragged(MouseEvent e) {
        if (isDrag) {
            curDrag.setPoint(e.getPoint());
            q.refresh();
            b.refresh();
            repaint();
        }
    }
    public void focusLost(FocusEvent e) {
        isDrag = false;
    }



    // 控制点区域
    @SuppressWarnings("serial")
    private class DragHandle extends Rectangle {
        private Point pt = new Point();

        DragHandle() {
            width = 5;
            height = 5;
        }
        DragHandle(Point p) {
            this();
            pt.x = p.x;
            pt.y = p.y;
            calcRect();
        }

        private void calcRect() {
            x = pt.x - 2;
            y = pt.y - 2;
        }
        public void setPoint(Point p) {
            pt.x = p.x;
            pt.y = p.y;
            calcRect();
        }
        public Point getPoint() {
            return pt;
        }
    }




    // Quadratic 曲线
    private class QuadCurve {
        // 曲线路径
        protected GeneralPath gp = new GeneralPath();
        // 曲线的几个控制点
        protected DragHandle []pt = null;

        protected QuadCurve () {}
        QuadCurve(Point p1, Point p2, Point p3) {
            pt = new DragHandle[3];
            pt[0] = new DragHandle(p1);
            pt[1] = new DragHandle(p2);
            pt[2] = new DragHandle(p3);
            refresh();
        }

        // 更新曲线
        public void refresh() {
            gp.reset();
            gp.moveTo((float) pt[0].getX(), (float) pt[0].getY());
            gp.quadTo((float) pt[1].getX(), (float) pt[1].getY(),
                    (float) pt[2].getX(), (float) pt[2].getY());
        }

        // 指定点是否在控制点区域内
        public DragHandle findDragHandle(Point p) {
            for (int i = 0; i<pt.length; i++)
                if (pt[i].contains(p)) return pt[i];
            return null;
        }
        // 获得所有控制点
        public DragHandle[] getDragHandles() {
            return pt;
        }
        // 获得该曲线的Path
        public GeneralPath getPath() {
            return gp;
        }
    }
    // Bezier 曲线
    private class BezierCurve extends QuadCurve {

        protected BezierCurve(Point p1, Point p2, Point p3) {}
        BezierCurve (Point p1, Point p2, Point p3, Point p4) {
            pt = new DragHandle[4];
            pt[0] = new DragHandle(p1);
            pt[1] = new DragHandle(p2);
            pt[2] = new DragHandle(p3);
            pt[3] = new DragHandle(p4);
            refresh();
        }

        public void refresh() {
            gp.reset();
            gp.moveTo((float) pt[0].getX(), (float) pt[0].getY());
            gp.curveTo((float) pt[1].getX(), (float) pt[1].getY(),
                    (float) pt[2].getX(), (float) pt[2].getY(),
                    (float) pt[3].getX(), (float) pt[3].getY());
        }
    }





    public void mouseClicked(MouseEvent e) {
    }
    public void mouseEntered(MouseEvent e) {
    }
    public void mouseExited(MouseEvent e) {
    }
    public void mouseMoved(MouseEvent e) {
    }
    public void focusGained(FocusEvent e) {
    }


    /**
     * @param args
     */
    public static void main(String[] args) {
        JFrame f = new JFrame("拖动几个橙色的小点看看 ^_^");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(640, 480);
        f.setLocationRelativeTo(null);
        f.add(new CurvesTest());
        f.setVisible(true);
    }

}

//EOF Read More...