2008年6月1日星期日

事件监听器支持类

因为要写一个带事件的类,所以就写了这个东西。废话不多说,直接贴代码了:


/*

* Copyrights (C) 2008 Bearice (Bearice@Gmail.com)

* Release under GNU/GPL Version 2.

*/

package cn.bearice.java.util.listener;



import java.lang.reflect.Method;

import java.util.ArrayList;

import java.util.Collection;

import java.util.Collections;

import java.util.EventListener;

import java.util.List;

import java.util.TreeSet;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;



/**

* 监听器支持。

* 同于扩展特定的监听器支持类。

* <p>提供监听器的添加,获取和移除服务。提供多监听器的全同步、全异步、顺序同步,顺序异步调用。</p>

* <p>调用侦听器时不保证侦听器的调用顺序。</p>

* <p>除非特别说明,传人任何等于null的参数都将引发NullPointerException</p>

*
@author Bearice

*/

public abstract class ListenerSupport<T extends EventListener> {



protected static final ExecutorService tp = Executors.newCachedThreadPool();

protected static final ExecutorService stp = Executors.newSingleThreadExecutor();

protected Collection<T> listeners = new TreeSet<T>();



private static class ListenerCaller implements Callable<Object> {



private Method method;

private Object[] args;

private EventListener listener;



public ListenerCaller(Method method, EventListener listener, Object[] args) {

this.method = method;

this.args = args;

this.listener = listener;

}



public Object call() throws Exception {

return method.invoke(tp, args);

}

}



/**

* 添加一个侦听器。

* <p>如果侦听器已经存在,则忽略。</p>

*
@param listener 要添加的侦听器

*
@throws NullPointerException listener等于null

*/

public void addListener(T listener) {

if (listener == null) {

throw new NullPointerException();

}

if (!listeners.contains(listener)) {

listeners.add(listener);

}

}



/**

* 移除一个侦听器。

* <p>如果侦听器不存在,则忽略。</p>

*
@param listener 要移除的侦听器

*
@throws NullPointerException listener等于null

*/

public void removeListener(T listener) {

if (listener == null) {

throw new NullPointerException();

}

if (listeners.contains(listener)) {

listeners.remove(listener);

}

}



/**

* 获取所有侦听器。

* 返回一个包含所有侦听器的<b>不可修改的</b>侦听器集合副本。

* 任何针对该副本的修改操作将引发异常

*
@return 侦听器集合副本。

*/

public Collection<T> getAllListeners() {

return Collections.unmodifiableCollection(listeners);

}



/**

* 全异步调用所有侦听器。

* <p>所有侦听器将在不同的线程中同时调用,该方法不会阻塞当前线程。</p>

* <p><b>异常处理:</b>侦听器可以抛出任何异常,但异常将被忽略</p>

*
@param method 要调用的方法对象

*
@param args 参数

*/

protected void fireEventAsync(Method method, Object... args) {

for (EventListener eventListener : listeners) {

tp.submit(new ListenerCaller(method, eventListener, args));

}

}



/**

* 全同步调用所有侦听器。

* <p>所有侦听器将在不同的线程中同时调用,该方法会阻塞当前线程并等待所有监听器调用全部返回之后才返回</p>

* <p><b>异常处理:</b>侦听器可以抛出任何异常,但异常将被忽略</p>

*
@param method 要调用的方法对象

*
@param args 参数

*/

protected void fireEventSync(Method method, Object... args) throws InterruptedException {

List<Callable> allCallable = new ArrayList<Callable>(listeners.size());

for (EventListener eventListener : listeners) {

allCallable.add(new ListenerCaller(method, eventListener, args));

}

tp.invokeAll((Collection) allCallable);

}



/**

* 顺序异步调用所有侦听器。

* <p>所有侦听器将在相同同的线程中顺序调用,该方法不会阻塞当前线程。</p>

* <p><b>异常处理:</b>侦听器可以抛出任何异常,但异常将被忽略</p>

* <p><b>调用顺序:</b>不确定调用侦听器的顺序</p>

*
@param method 要调用的方法对象

*
@param args 参数

*/

protected void fireEventAsyncSeq(Method method, Object... args) {

for (EventListener eventListener : listeners) {

stp.submit(new ListenerCaller(method, eventListener, args));

}

}



/**

* 顺序同步调用所有侦听器。

* <p>所有侦听器将在相同的线程中顺序调用,该方法会阻塞当前线程并等待所有监听器调用全部返回之后才返回</p>

* <p><b>异常处理:</b>侦听器可以抛出任何异常,但异常将被忽略</p>

* <p><b>调用顺序:</b>不确定调用侦听器的顺序</p>

*
@param method 要调用的方法对象

*
@param args 参数

*/

protected void fireEventSyncSeq(Method method, Object... args) throws InterruptedException {

List<Callable> allCallable = new ArrayList<Callable>(listeners.size());

for (EventListener eventListener : listeners) {

allCallable.add(new ListenerCaller(method, eventListener, args));

}

stp.invokeAll((Collection) allCallable);

}

}

非安全线路上的密码传输与登录

这个东西是给新版出校做准备而写的。因为校园网里,密码嗅探和会话劫持的几率比较大,所以不能将密码的明文,或者固定的哈希在网络上发送。这样就需要使用 一个动态的密码验证协议。本来是打算使用类似NTLM那样的挑战/相应式的验证的,但是后来大树说其实可以用随机值加md5就可以。虽然MS NTLM的安全性更好,但是相对比较复杂。所以后来就使用了后面一种实现方式。
大致流程如下:
1:服务器启动,初始化一个RSA密钥对
2:客户向服务器发送预登录请求,服务器生成一个随机值R,一个时间戳T,用生成的密钥对R和T签名得到签名S,吧R、T和S返回给客户
3:客户端收到R、T、S,把R和密码的unicode拼接在一起,求出哈希值H1,然后吧S、R、T、H1和用户名U发送到服务器。
4:服务器接收到S、R、T、H1、U以后,首先检查R与T的签名是否符合S,确定R与T的真实性后检查T是否超时,之后根据U取得用户的密码,与R拼接,求哈希H2,然后检查H1是否等于H2。这些步骤中间任何一步出错将导致验证失败。

这个方法的Java实现我正在写,等写完了再发上来。

某个Google控又回来啦

某人一向控Google而无视Baidu,奈何前些天Google pages 抽风,连接不能。经过骨头同志介绍,转战Blogger。等Google Pages 修复以后吧以前的资料弄回来~

2008年4月30日星期三

关于高斯模糊和不完备Java实现

前两天发图片马赛克时青姐问的,今天没事研究了一下:
1)什么是高斯模糊:
高斯模糊,是依靠正态分布函数的图像模糊算法,我们知道,正太分布函数曲线是中间高,两边低的对称图形,周边像素与某像素的距离为自变量,用这个函数的取值当作系数,分别乘以周边像素的ARGB值,然后把结果相加,可以得到一个新的ARGB,这个值就是模糊后图像的对应像素的ARGB值。
由于正太分布的曲线是中间高两边低,所以所得的像素与原来的像素相似度最大,也就基本上保持了原来图形的基本形状,并且其边缘也被模糊了
上面的话是我自己打的,可能有些表述不清,具体的可以到Wikipedia上搜索一下,那里的表述是很详细的。
2)高斯模糊的具体实现:
首先要生成高斯系数矩阵:

public static double[] gMatrix(double s, int r) {
double[] ret = new double[r];
double a = 1 / (2d * s * s);
double b = Math.sqrt(a * (1 / Math.PI));
double c = Math.exp(a);
for (int i = 0; i < r; i++) {
ret[i] = b * Math.pow(c, -i * i * 1d);
//ret[i+r-1] = Math.pow(Math.sqrt(Math.PI*2D*s*s), -1)*Math.exp((-i*i)/(2*s*s));
}
return ret;
}

然后是针对每像素操作的函数:

public static byte[] getARGB(int j) {
byte b = (byte) (j & 0x000000ff);
byte g = (byte) ((j & 0x0000ff00) >> 8);
byte r = (byte) ((j & 0x00ff0000) >> 16);
byte a = (byte) ((j & 0xff000000) >> 24);
return new byte[]{a, r, g, b};
}

public static int getInt(byte[] bytes) {
//System.out.println(Arrays.toString(d));
return ((bytes[0] << i =" 1;" i =" 1;" a =" src[i]" b =" p[i]" x =" (a" d="%d" x="%x]\n" x =" x"> 0xFF ? 0xFF : x;
src[i] = (byte) (x & 0xff);
}
}

下面是针对一个图像行(横线或纵向)的操作

public static int[] smoothLine(int[] data, double[] mtx) {
int[] ret = data.clone();
for (int i = 0; i < d =" getARGB(data[i]);" k =" 1;" nd =" getARGB(data[i">= 0) {
byte[] nd = getARGB(data[i - k]);
procByte(nd, mtx[k]);
// System.out.println("" + (i - k) + "," + Arrays.toString(nd));
addByte(d, nd);
// System.out.println("plus:" + Arrays.toString(d));
}
}

ret[i] = getInt(d);
//System.out.printf("final:%s 0x%08X\n", Arrays.toString(d), ret[i]);
}
return ret;
}

最后是针对整个图像的操作:

public static BufferedImage smooth(BufferedImage i, double s, int r) {
BufferedImage ret = new BufferedImage(i.getWidth(),

i.getHeight(),

i.getType());
double[] mtx = gMritex(s, r);
for (int j = 0; j < i.getHeight(); j++) {
int[] data = i.getRGB(0, j, i.getWidth() - 1, 1,

null, 0, i.getWidth());
//System.out.println(Arrays.toString(data));
data = smoothLine(data, mtx);
//System.out.println(Arrays.toString(data));
ret.setRGB(0, j, i.getWidth() - 1, 1,

data, 0, i.getWidth());
}
// for (int j = 0; j < i.getWidth(); j++) {
// int[] data = ret.getRGB(j, 0, 1, i.getHeight()-1, null, 0, i.getHeight());
// //System.out.println(Arrays.toString(data));
// data = smoothLine(data, mtx);
// //System.out.println(Arrays.toString(data));
// ret.setRGB(j, 0, 1, i.getHeight()-1, data, 0, i.getHeight());
// }
return ret;
}

这里需要说明几点:本来高斯模糊是矩阵运算的,但是可以化简为线性运算,也就是横竖各处理一次(至于原因参看Wikipedia)但是我发现使用 JavaAPI对竖向像素列操作十分费时,所以就把它暂时注释掉了。但是这样模糊的结果就不是完全高斯模糊的结果。我想应该吧横向处理过的图像做90度翻转,然后再横相处理一次。不过这个想法还没顾得上实施。有时间再写。
另外一个问题是:处理过的图像最右边会有一道黑线,不知道是为什么,希望有高人能够说明一下。

刚才发现好多正态分布都写成了正太分布……感觉好邪恶的样子=_=|||
最后是完整代码,里面还有Wikipedia的高斯模糊的词条页,省的伟大的GFW和谐掉我们的网络。

2008年4月5日星期六

你可能不知道的:突破Java单例模式

这个东西是以前写代码注入时发现的。这两天实在无聊,所以写出来,仅供参考。MS没什么使用价值。。。。权当娱乐
我们知道,在Java中有一种单例模式,是在一个虚拟机里,某个对象只有一个实例。但是,真的只能有一个实例么?
考虑下面代码:


import java.io.InputStream;
public interface IOnlyOne{
void printOne();
}
public class OnlyOne implements IOnlyOne{
private static final OnlyOne myOne = new OnlyOne();
public void printOne(){
System.out.println(myOne);
}
public static void main(String[] args) throws Exception{
myOne.printOne();
OnlyOneLoader loader = new OnlyOneLoader();
IOnlyOne o = loader.createNewOne();
o.printOne();
}
}
public class OnlyOneLoader extends ClassLoader{
public IOnlyOne createNewOne() throws Exception{
InputStream is = getClass().getResourceAsStream("OnlyOne.class");
byte[] b = new byte[is.available()];
is.read(b);
Class clz = defineClass(null,b, 0, b.length);
Object o = clz.newInstance();
return (IOnlyOne) o;
}
}

我们看到createNewOne()首先定义了一个类OnlyOne,然后实例化了一个对象。那么根据代码来看,这个对象的printOne方法又会访问那个myOne的静态成员。看起来两次打印的结果应该是相同的,但是事实上,结果并不相同。
那么,这是为什么呢?是不是定义类过程中又对那个myOne对象重新赋值了呢?我们可以在o.printOne();后面再加上一句myOne.printOne();
可以看到,打印的和第一次的结果是一样的。这就说明了并不是重新赋值,而是有两个myOne的变量存在。
事实上,Java中,一个类是可以存在多个定义的,这些类的类名可以相同,但是确可以有各自的成员存储区,他们的代码也可以不同。在同一个 ClassLoader中,不允许出现重名类,但是Java却允许多个不同的ClassLoader同时存在。需要注意的是:即使类名和代码都相同,但是不同ClassLoader说产生的类是不兼容的,这就是为什么上述代码中要引入一个接口的原因了。你可以把那个接口去掉,看看会发生什么有趣的现象~~
那么,这个和单例模式有什么关系呢?因为单例的对象实例是存储在静态成员变量中的,所以在不同的ClassLoader下,这个变量的值可能是不同的。

2008年4月4日星期五

没事干,PS两张证书来玩

最近看Gundam Seed,无聊之时就PS两张认证出来。猜猜原来的认证是什么证书的?