[翻译]Eclipse里的IAdaptable是什么?

原文地址:http://www.eclipsezone.com/articles/what-is-iadaptable/

IAdaptable在Eclipse里是一个非常重要的接口。对于Eclipse开发老手来说,它就像异常处理和抽象类一样寻常;但是对新手而言,它却令人感到困惑和畏惧。这篇文章将向你解释IAdaptable到底是什么,以及它在Eclipse里起到的作用。

类型转换

Java是所谓的强类型语言,也就是说,每个实例都对应一个类型。其实类型分为两种:声明类型和运行时类型(也分别被称为静态类型和动态类型)。像Python这样的弱类型语言常被称为无类型的语言,其实严格说来不是这样,因为每个实例都对应一个运行时类型,只是你并不需要声明这一点而已。

现在回到Java,为了能够执行一个类的某个方法,这个方法必须在声明类型中可见,换句话说,即使在运行时实例是某个子类型,你也只能执行那些父类型里定义的方法。

List list = new ArrayList();
list.add("data");       // 正确,add是List里定义的方法
list.ensureCapacity(4); // 不正确,ensureCapacity()只在ArrayList被定义

如果一定要执行特定类型的方法,我们必须先强制转换这个实例到正确的类型。对于上面的例子,我们可以将list转换为ArrayList(译注:原文In this case, we can cast ArrayList to List,怀疑是笔误),因为ArrayList实现了List接口,你甚至可以在运行时通过instanceof关键字检验list是否为ArrayList的一个实例。

可扩展的接口

不幸的是,一个类可能并没有实现你需要的接口,这样就无法进行强制类型转换了。原因有很多,比如只在少数情况下才需要这个接口,或者你需要的接口是在另一个不相关的库里,又或者接口是有了类以后才开发出来的,等等。

这时你就需要IAdaptable了。可以把IAdaptable想象为一个能够动态进行类型转换的途径。对比下面的直接类型转换:

Object o = new ArrayList();
List list = (List)o;

换一种方式,我们可以这样做:

IAdaptable adaptable = new ArrayList();//译注:这里的ArrayList应该不是指java.util.ArrayList
List list = (List)adaptable.getAdapter(java.util.List.class);

这就是上面所说的动态类型转换,我们所做的事情是试图把adaptable转换为一个List实例。

那么,当可以直接转换的时候为什么要费这个力气通过getAdapter()来转换呢?其实这种机制可以让我们将目标类转换为它并没有实现的接口。举个例子,我们可能想把一个HashMap当作List来用,尽管这两个类的性质并不相同,可以这么做:

IAdaptable adaptable = new HashMap();//译注:这里的HashMap应该不是指java.util.HashMap
List list = (List)adaptable.getAdapter(java.util.List.class);

实现IAdaptable接口

大部分IAdaptable的实现是一些if语句的叠加,比如我们现在要实现HashMap的getAdapter()方法,它看起来可能是这样:

public class HashMap implements IAdaptable {
  public Object getAdapter(Class clazz) {
    if (clazz == java.util.List.class) {
      List list = new ArrayList(this.size());
      list.addAll(this.values());
      return list;
    }
    return null;
  }
  ...
}

所做的就是返回一个适配器(adapter,更确切的说是一个副本),而不是进行直接的类型转换。如果参数类型没有被支持,惯例是返回null值(而非抛出异常),代表这个方法失败了。因此,在调用这个方法时,不应该假定它总是返回非null值。

PlatformObject

当然,如果你希望增加一个新的被支持的adapter类型时必须编辑这个类才行(译注:在getAdapter()里增加更多的if语句),这会比较辛苦。而且,既然你已经知道了这个类型,何不直接修改接口声明呢?其实有很多原因使得你并不希望直接编辑这个类(例如更容易保持向下兼容性),也不想改变它的类型(HashMap虽然不是一个List,但可以转换过去)。

Eclipse通过PlatformObject抽象类来解决以上问题,它为你实现了IAdaptable接口,Eclipse平台(Platform)提供了IAdapterManager的一个实现,并且可以通过Platform.getAdapterManager()访问到,它把所有对getAdapter()的请求(调用)委托给一个名为IAdapterManager的东西。你可以将它想象为一个巨大的保存着类和adapter信息的Map,而PlatformObject的getAdapter()方法会查找这个Map。

适配已存在的类

这样,PlatformObject不需要重新编译就能够支持新的adapter类型,这一点在Eclipse里被大量使用以支持workspace的扩展点。

现在假设我们想要将一个只包含String类型元素的List转换为一个XMl节点,这个节点的格式如下:

<List>
  <Entry>First String</Entry>
  <Entry>Second String</Entry>
  <Entry>Third String</Entry>
</List>

因为toString()方法可能有其他用途,我们不能通过覆盖toString()方法来实现这个功能。所以,我们要给List关联一个工厂类以处理XML节点类型的适配请求。要管理工厂类需要以下三个步骤:

1、由List生成一个Node,我们把这个转换过程用IAdapterFactory包装起来:

import nu.xom.*;
public class NodeListFactory implements IAdapterFactory {
  /* 可以转换到的类型 */
  private static final Class[] types = {
    Node.class,
  };
  public Class[] getAdapterList() {
    return types;
  }
  /* 转换到Node的功能代码 */
  public Object getAdapter(Object list, Class clazz) {
    if (clazz == Node.class && list instanceof List) {
      Element root = new Element("List");
      Iterator it = list.iterator();
      while(it.hasNext()) {
        Element item = new Element("Entry");
        item.appendChild(it.next().toString());
        root.appendChild(item);
      }
      return root;
    } else {
      return null;
    }
  }
}

2、把这个工厂类注册到Platform的AdapterManager,这样当我们希望从List的实例中获得一个Node实例时,就会找到我们的工厂类。注册一个工厂类的方式也很简单:

Platform.getAdapterManager().registerAdapters(
  new NodeListFactory(), List.class
);

这条语句将NodeListFactory关联到List类型。当从List里请求adapter时,Platform的AdapterManager会找到NodeListFactory,因为在后者的getAdapterList()方法的返回结果里包含了Node类,所以它知道从List实例得到一个Node实例是可行的。在Eclipse里,这个注册步骤一般是在plugin启动时完成的,但也可以通过org.eclipse.core.runtime.adapters扩展点来完成。

3、从List获得Node,下面是例子代码:

Node getNodeFrom(IAdaptable list) {
  Object adaptable = list.getAdapter(Node.class);
  if (adaptable != null) {
    Node node = (Node)adaptable;
    return node;
  }
  return null;
}

总结

综上所述,要在运行时为一个已有的类增加功能,所要做的只是定义一个用来转换的工厂类,然后把它注册到Platform的AdapterManager即可。这种方式在保持UI组件和非UI组件的分离方面特别有用。例如在org.rcpapps.rcpnews.ui和org.rcpapps.rcpnews这两个plugin里,前者的IPropertySource需要与后者的数据对象(data object)相关联,当前者初始化时,它将IPropertySource注册到Platform,当数据对象在导航器(navigator)里被选中的时候,属性视图里就会显示正确的属性。

显然,java.util.List并不是PlatformObject的子类,所以如果你希望能够编译这里所说的例子,必须建立一个List的子类型。注意,可以直接实现IAdaptable接口,而非必须继承PlatformObject抽象类。

public class AdaptableList implements IAdaptable, List {
  public Object getAdapter(Class adapter) {
     return Platform.getAdapterManager().getAdapter(this, adapter);
  }
  private List delegate = new ArrayList();
  public int size() {
    return delegate.size();
  }
  ...
}

最后,例子里生成XML的部分使用了XOM的类库。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/09/24/243312.html

SWT的PaintListener

以前很少用到这个类(org.eclipse.swt.events.PaintListener),利用它可以用来在control上画一些东西,基本方法是在control上 addPaintListener()一个PaintListener,然后在这个listener里做具体的画图工作,listener在control需要绘制的时候调用。

下面例子代码用来在一个composite的中央绘制一行文字。

package com.test;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class Test3 {

    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setLayout(new FillLayout());
        final Button button = new Button(shell, SWT.PUSH);
        button.setText("This is a button");
        final Composite comp2 = new Composite(shell, SWT.BORDER);
        comp2.addPaintListener(new PaintListener() {
            public void paintControl(PaintEvent e) {
                String text = "This is a composite";
                Rectangle area = comp2.getClientArea();//client area
                int tw = calcTextWidth(e.gc, text);//text width
                int th = e.gc.getFontMetrics().getHeight();//text height
                Rectangle textArea = new Rectangle(area.x + (area.width - tw) / 2,
                        area.y + (area.height - th) / 2, 
                        tw,
                        th);//centerized text area
                e.gc.drawString(text, textArea.x, textArea.y);
                e.gc.drawRectangle(textArea);
            }

            private int calcTextWidth(GC gc, String text) {
                int stWidth = 0;
                for (int i = 0; i < text.length(); i++) {
                    char c = text.charAt(i);
                    stWidth += gc.getAdvanceWidth(c);
                }
                return stWidth;
            }
        });
        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }
        display.dispose();
    }
}

运行结果如下图:

file

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/09/23/242837.html

Draw2d里的Invalidating和Updating

本文部分内容来自Building a Database Schema Diagram Editor with GEF和GEF and Draw2d Plug-in Developer Guide,是对Draw2D里一些基本概念的说明。

LayoutManager(布局管理器)

  • 布局管理器通过Figure#setBounds()改变子图形的位置和大小。
  • 根据布局算法和子图形决定当前图形的preferredSize。
  • 布局的过程是先确定图形的大小,再计算每个子图形的新位置和大小。 

Figure#invalidate()

  • 若valid属性已经是false则直接返回。
  • 如果图形拥有LayoutManager,则调用LayoutManager的invalidate()方法,在XYLayout里作用是将preferredSize重置为null值,在FlowLayout里还要把minimumSize置为null值。
  • 将图形的valid属性置为false。

Figure#revalidate()

  • 我觉得它实际代表"recursive invalidate"的意思。这个方法的功能是首先将图形自己invalidate(),然后递归的将图形的父图形invalidate(),一直到根图形为止,这个根图形会被加入到UpdateManager的一个列表中。
  • 在Figure的很多方法里,如setBorder()、setContstraint()、setLayoutManager()、add()、remove()等,会自动调用revalidate()方法。因此,大部分情况下我们不需要手动调用这个方法。

Figure#validate()

  • 若valid属性已经是true则直接返回。
  • 将图形的valid属性置为true。
  • 如果图形拥有LayoutManager,则调用LayoutManager的layout()方法。
  • 对图形的每个子图形,调用validate()方法。

Figure#repaint()

  • 在图形的UpdateManager里,将图形所处的区域标记为“脏”区域,这个区域将由UpdateManager(定期)重画。
  • 在图形的setVisible()、setOpaque()、setForegroundColor()、setBounds()、setBackgroundColor()等方法里会自动调用repaint()方法。

Figure#paint()

  • 虽然名称相似,但这个方法和repaint()关系不大。在Figure里这个方法按顺序调用paintFigure()、paintClientArea()和paintBorder()这三个方法,当实现自己的Figure时,绝大多数情况下应该只覆盖paintFigure()而不是paint()本身。

Figure#getPreferredSize()

  • 对于Label这样的图形,它的preferredSize由它所显示的文本和图标所占空间决定;如果一个图形包含子图形,则它的preferredSize要考虑子图形的排列方式,所以要由LayoutManager来决定。
  • LayoutManager的getPreferredSize()方法还有两个参数:wHint和hHint,它们分别代表图形的已知长(宽)度,如果其中一个值是大于零的,则在另一个方向上子图形将换行(列)排列,以保证长(宽)度不大于这个已知值。

基本上来说,validate是对于尺寸的调整,而repaint()是对颜色的调整。当我们把一个图形C作为子图形拖到另一个图形P里的时候(想象P为UML类图里表示类的矩形,C为表示属性或方法的矩形),因为调用了P的add()方法,所以P及P的所有“祖先”图形都将通过revalidate()被置为invalid状态。UpdateManager随后在performUpdate()里对这些图形进行validate(),在validate()的过程中,每个图形将通过自己的LayoutManager重新计算自己的尺寸。这样就实现了P随子图形的多少自动改变大小。

file

上面左图是在子图形上发生改变时,自动调用了Fig4的invalidate()方法,导致到根图形之间的所有图形的invalidate()方法被触发。右图则是UpdateManager对这些invalid图形进行validate(),并且是自上而下进行的(几乎可以认为validate()方法就是对layout()方法的调用)。注意到由于对Fig2进行了layout(),Fig5的尺寸也可能因此发生改变,如果发生了这种情况,则Fig5的invalidate()方法也会被调用。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/09/15/237923.html

[GEF]实现模板功能

最近遇到一个需求是要在GEF应用里实现“模板”的功能,也就是像word那样在新建一个文件的时候,用户可以选择一个模板,然后以这个模板为基础进行编辑,而不用每次都从头开始。同时,用户要能够创建新模板和对模板进行编辑,在模板里可以指定一些对模型的限制,例如树的最大高度、子节点数目的限制,等等。

最简单的模板可以就是一个实例,以它为模板时创建的新实例就是这个实例的副本。这种情况下,模板编辑器就是实例编辑器。问题主要有两个,一是无法通过模板实现上述对实例的限制,二是除了扩展名以外,用户难以通过编辑器外观区分是在编辑模板还是编辑实例(本文中实例指原有的那个模型)。

更一般的模板可以这样设计:首先,模板模型和实例模型是很相似的,只是在实例模型的基础上,每个模板类多了一些用来限制实例的属性,因此,模板模型中的每个类应该是实例模型中对应类的子类,这样就解决了限制实例的问题。其次,对模板的编辑不能使用现有的Editor,而应该是对现有Editor的扩展,目的是区分两种编辑器的外观。

等一下,按照这个设计在实现上会有一些问题,在根据某个模板编辑实例的时候,必须知道模板信息,这样才能判断比如新增子节点操作是否可用。但是如果你某天打开一个实例时模板早已被删除了呢?我们实际需要的是,一旦实例被创建,它就与模板脱离关系,即使模板被修改和删除。因此,我认为模板和实例必须共享完全相同的模型,这样实例中就有完整的限制信息,并且还有一个额外的好处,它们的读取方法是完全一致的,可以节约不少工作量。

现在来看看实现。为了更加灵活,我们把模板单独作为一个新的Plugin,这样一是便于分开代码,二是很方便添加或移除模板功能(只要删掉模板Plugin就能移除模板功能)。因此,在模板Plugin里要额外指定对原来Plugin的依赖(在Plugin编辑器的dependencies属性页里),而原来Plugin里要确保Export了模板Plugin需要的所有包(在runtime属性页里)。

在这个Plugin里,创建一个继承实例Editor的新Editor(名为TemplateEditor)。怎样让这个Editor里显示的界面与实例Editor有所区别呢?最简单的一个方法是设置viewer的背景颜色,也就是覆盖configureGraphicalViewer()方法,加上下面这句:

getGraphicalViewer().getControl().setBackground(new Color(null, 250, 250, 200)); 

如此一来,这个Editor的背景就是淡黄色了。但我还想进一步改造它,让每个节点的显示都与实例编辑器中不同。因为节点的figure是在editpart里创建的,所以就必须使用与原来不同的editpart,而editpart是通过partfactory创建的,所以要让viewer使用与原来不同的partfactory:

getGraphicalViewer().setEditPartFactory(new TemplatePartFactory()); 

当然,我们还要通过继承原来的那些editpart得到一套新的editpart,在TemplatePartFactory的createEditPart()方法里返回它们。在新的editpart的createFigure()方法里,返回你希望的图形。

file

图:模板编辑(左图)和实例编辑(右图)

图形的问题解决了,还剩下另一个问题,应该只在编辑模板的时候在properties view里显示那些限制属性,而在编辑实例时不显示(这些属性在起作用,但不应该能被编辑)。按照GEF提供的例子,一般是在model类里定义用来控制哪些属性显示在properties view的IPropertyDescriptor数组,我们现在的情况是两个Plugin使用同一个model,这样就造成了矛盾。解决的办法是改为在editpart里定义IPropertyDescriptor数组。GEF的editpart通过getAdapter()方法返回实现IPropertySource接口的对象:

public Object getAdapter(Class key) {
    if (IPropertySource.class == key) {
        if (getModel() instanceof IPropertySource)
            return getModel();
        if (getModel() instanceof IAdaptable)
            return ((IAdaptable)getModel()).getAdapter(key);
    }
    if (AccessibleEditPart.class == key)
        return getAccessibleEditPart();
    return null;
}

可以看到当model类实现了IPropertySource时,就会返回model类。我们要覆盖这个方法,让它返回editpart本身,同时让editpart实现IPropertySource并把原来放在model类里的那些代码移动到editpart里。最好在原来的editpart里就做出修改,这样模板部分只要简单的继承下来并修改getPropertyDescriptors()等几个相关方法即可:

public Object getAdapter(Class key) {
    if (IPropertySource.class == key) {
        return this;
    }
    return super.getAdapter(key);
}

file

图:模板模型的属性视图(左图)和实例模型的属性视图(右图)

要记住,为了让这些限制属性在实例编辑中起作用,必须修改原来的代码,增加这些额外的判断。之后,再在原来的CreationWizard里增加一个选择模板的page,模板功能就算实现了。按照这个思路,不需要额外写很多代码。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/09/07/232095.html

导出Plugin的一点心得

Eclipse升级到了3.1以后,在Plugin manifest editor上做了相当大的改动,可能是刚刚完全支持OSGi的缘故吧,感觉这个editor的小bug挺多的。比如很多情况下表示dirty的星号不会消失,有时会弹出带红叉的对话框等等,不过我相信在eclipse 3.2版本里会得到解决,因为关于它的bug report已经不少了。

除了一些小bug以外,我发现在3.1里导出一个Plugin也变得有些“莫名其妙”,最近经过实验摸索,得到以下一些心得:

1、开发plugin的时候,不要在project properties的java build path里做任何修改,应该在plugin配置文件的dependencies、runtime和build这几个部分添加。

2、在build页上面部分,Library的缺省情况是".",我发现有时候指定为"."会造成class不被输出。比较保险的方法是指定一个.jar名字,对应的folder还是src/,勾中下面的Include selected library选项。

3、在build页中间部分指定要输出的文件,一般除了缺省的那些以外,还要包含如lib、icons、log4j.properties等目录或文件。

4、在build页下面部分,也就是“Extra Classpath Entries”,指定你的应用程序用到的外部包,例如log4j.jar等等。

5、需要注意的是,如果Library里指定了abc.jar,则输出时会生成abc.jar这个文件,但Plugin在运行时会报ClassNotFound异常,原因是你没有把abc.jar指定到这个Plugin的classpath里。解决的办法是在Manifest.mf文件的Bundle-ClassPath项里手动加上abc.jar,例如“Bundle-ClassPath: abc.jar,lib/log4j-1.2.11.jar”这样。

6、若你有两个Plugin,多个的情况也一样,假设B依赖A,那么不仅在B的dependencies里要加上A,还要保证A的Runtime页里export了B所需的所有包,也就是在A的Manifest.mf文件里指定正确的“Export-Package”项。

7、输出为目录或输出为单个jar包在运行时没有什么区别,感觉jar包方便一些,输出的jar包名就是项目名。

希望对你有帮助。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/09/06/230759.html

写代码的代码:JET

用过EMF的人想必都对它的代码生成功能印象深刻吧,有没有想过这是怎样实现的呢?

代码生成一般是通过写好的模板,在用户输入一些限制条件后,由程序把这两者结合起来得到需要的代码。EMF也是这样,它内置了一些模板(放在 org.eclipse.emf.codegen.ecore里),我们通过Java Interface或XML Schema文件建立ecore模型后(这一步由org.eclipse.emf.codegen.ui支持),插件 org.eclipse.emf.codegen帮我们生成model、edit、editor和test这几个新的插件,这些生成的插件就可以被用来构 造应用程序了。

EMF使用JET(Java Emitter Templates)进行代码生成,JET使用的模板文件与JSP格式几乎完全一致,像JSP里有application这样的隐含变量一样,在JET模 板文件里可以使用argument这个隐含变量,它代表用户的输入参数(对EMF来说就是ecore模型)。下面是一个最简单的带有参数的模板文件 hello.txtjet,JET模板文件的扩展名一般是要生成文件类型再加上jet。

<%@ jet package="hello" class="GreetingTemplate" %>
Hello, <%=argument%>!

只要给这个模板文件一个参数,JET就能帮我们生成一个字符串,其内容是“Hello,参数!”。如果我们定义一个.javajet的模板,配合适当的参数,再把生成的字符串保存为文件,就得到了java代码,EMF的代码生成过程就是如此。

自己实现代码生成器的过程如下:创建一个Plugin项目,编写模板文件.javajet,一般放在templates目录下;定义元模型,这个元 模型要与模板相匹配,模板的argument一般就是元模型最外层的container;构造一个用户界面,让用户可以构造元模型的实例;提供一个 Action比如按钮,用户按下即开始生成代码。

代码的生成是由JETEmitter完成的,它会在workbench里先生成一个.JETEmitter项目,把模板转换为java类(这个过程类似JSP转换为Servlet),然后调用这些类的generate()方法得到结果。

我做了一个生成.txt文件的生成器插件,点此下载。安装后在Eclipse主菜单里选File->New->Others,在New对话框里选择Sample Wizards下的Echo filename text file,新建的文本文件内容里会包含文件名。

因为用途有限,JET的资料不是很多,这里有两篇:链接1链接2,其中后者在EMF的帮助里也找得到。

最后补充一个Tip,在plugin里访问一个相对路径文件的方法如下:

String base = Platform.getBundle(pluginId).getEntry("/").toString();
String relativeUri = "templates/echo.txtjet";
JETEmitter emitter = new JETEmitter(base + relativeUri, getClass().getClassLoader());

更正(08/23),上面说的方法只对JETEmitter有效,得到的路径是Platform内部路径而非本地路径,这里提供另一个方式可以得到本地路径:

Platform.asLocalURL(Platform.getBundle(pluginId).getEntry("/log4j.properties"))

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/08/16/215697.html

感受Ruby on Rails

最近看到几篇介绍Ruby on Rails(RoR)的文章,忍不住自己也下载了一份来体验一下,简单说说感受。

参考文档建立了一个很简单的系统,没花多少时间,这主要归功于scaffold函数提供了缺省的web界面。要想修改这个界面却不那么简单,配置上只要修改一两处,但必须手写一个.rhtml模板文件,这里面存在不少代码,而且ruby代码和html代码交叉得很厉害,和asp差不太多。当然,我想这一步是使用任何框架都无法避免的,看到有文章说RoR的开发效率是struts的十倍,我保留意见。

RoR另一个提高开发效率的途径是做了很多假设来代替配置文件,例如控制文件都放在controller目录里,模型文件都放model目录,url映射就是控制文件名的前半部分,数据库表名与model的对应,等等。我很赞同这种方式,一是节约了写一堆xml配置文件的时间,二是任何熟悉RoR的人都能很快找到需要的类。

由于对Ruby并不熟悉,所以我看.rb文件里的代码会比较吃力。Ruby是解释型的语言,它在语法上有一些方便之处,例如变量的表示;而且它是比较彻底的面向对象语言,连数字123都是对象;三是省略了编译这个步骤,源代码修改后可以立即生效(Eclipse的增量编译基本上也可以达到这个效果);缺点应该是主要是性能方面,我想很可能比不上jsp。

长时间使用一种语言后,总想偶尔换换口味。Java是我最喜欢的无疑,同时也很羡慕掌握多门语言的高手,碰到问题先考虑用哪种语言实现,毕竟每门语言都有自己擅长。继续研究研究Ruby,也许它会成为我的另一杆枪。

另外,RDT是一个Ruby开发的Eclipse插件,但对RoR似乎没有特别的支持。除了.rb文件的编辑器外,它还专门集成了一个正则表达式验证工具,看来Ruby在这方面也比较在行。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/08/13/214329.html

在SWT里显示AWT对象

今天遇到一个问题,就是要在一个Eclipse插件里显示JFreeChart的图形,因为后者是基于Java2D的,要把图形显示在SWT应用程序里需要利用SWT-AWT桥接器来实现,虽说桥接的方式多半会伴随着性能下降,但总归是一个解决方法。

代码并不复杂,以下是一个片断:

public void createPartControl(Composite parent) {
    parent.setLayout(new FillLayout(SWT.VERTICAL));
    Composite drawarea = new Composite(parent, SWT.EMBEDDED);
    drawarea.setLayout(new FillLayout());
    Frame canvasFrame = SWT_AWT.new_Frame(drawarea);
    canvas = new java.awt.Canvas() {
        public void paint(Graphics g) {
            super.paint(g);
            if (chart != null)
                chart.draw((Graphics2D) g, getBounds());
        }
    };
    canvasFrame.add(canvas);
}

关键之处在于SWT_AWT.new_Frame()方法,得到的是一个java.awt.Frame对象,要显示的AWT内容都放在它上面就好。

BTW, SWT下免费的图表工具好象很少,只能暂时先这样使用JFreeChart了。

Update: 如果要在SWT里显示带有动画效果的AWT图形,最好在Frame上先放一个JPanel这样的带有双缓冲的控件,否则图象在运动时会产生明显的闪烁。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/07/17/194710.html

通过OCP考试

二十天没有写新日志,真对不起关心这个blog的朋友们。

从四月份开始准备了三个月,这些天参加了Oracle 9i的培训,并顺利通过了OCP全部四门课的考试。虽然证书还要一个多月才能拿到,但总算可以暂时把心放下了。目前的工作和Oracle没有直接关系,考这个一是为拿证书(以前从没考过认证),证明自己的学习能力;二是强迫自己对数据库产品进行比较全面的了解。

Oracle大学的高老师很有经验,十分感谢他,让我们在了解知识点的同时还掌握了不少对实际出现情况的应对方法,同时也感受到考过OCP绝不代表你可以处理全部问题了,没有一两年的实际经验,对于生产数据库还是少碰为妙,这和背背书应付考试完全两个概念。

虽说培训环境还不错,可每天来回路上挤三个多小时的车,我还是快坚持不住了。这周体检出血液中某种细胞低于正常值,医生说是累的,联想到程序员王俊,以及再早些我校青年教师的突然去世,深切体会到“身体是革命的本钱”所言极是,同志们都要注意那!

这个月开始又要和自己喜欢的Eclipse打交道了(Eclipse 3.1正式版已经发布),我一有时间还是会在这里写下所学的心得体会,常来看看哦。

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/07/02/184956.html

一个用OWL-S组装Web服务的例子

OWL-S可以用来描述Web服务,这个帖子将介绍一个非常简单的例子,也许对理解Web服务的组装有些作用。这个服务是对已有Web服务进行组装和执行,所以你并不需要发布自己的Web服务。你需要安装ProtegeOWL-S Editor插件,我用的版本前者是3.1 beta build 191,后者是build 15,它们在一起运行得还不错。

所用的Web服务在http://www.bs-byg.dk/hashclass.wsdl,它包含两个方法:HashString和CheckHash,前者用指定编码方式(MD5、SHA1等等)对指定字符串编码,后者根据指定编码方式检查一个字符串(HashString)是否是另一个字符串(OriginString)的编码结果。我们将把这两个方法组装成一个服务,对输入的编码方式和待编码字符串先进行编码,然后检查编码的结果是否正确,如果正确返回true,否则返回false。下面是组装步骤,完整的工程在这里下载

1、确认你的OWL-S Editor已经安装到Protege里,启动Protege,新建一个owl文件类型的工程,在菜单project->config里勾选上owls选项,按确定后Protege的主界面会多出一个OWL-S Editor页。

2、转到OWL-S Editor页,按左上角的WSDL按钮,在弹出的对话框里输入Web服务的地址http://www.bs-byg.dk/hashclass.wsdl,然后按回车,过一会儿在对话框里会显示出这个Web服务的信息,左边是Operations列表。

file
图1 用来导入WSDL的对话框

3、因为每次只能import一个Operation,所以先选择HashString,然后按右下方的Import按钮,这时系统会提示要把生成的owls文件(扩展名为.owl)保存在一个位置,你可以选择任何位置。

4、使用同样的方法把CheckHash方法也导入进来,这样我们就有了两个可用于组装的Web服务了。如果你愿意的话,可以单独执行看看,方法是选择一个Service,然后按绿色的执行按钮。

file
图2 导入的两个服务

5、现在开始组装它们。为此我们新建一个Service实例(按Create Service按钮)、一个Profile实例、一个CompositeProcess实例和一个WSDLGrounding实例,分别命名为myservice、myprofile、myprocess和mygrounding好了。

6、接下来把它们连接起来,首先选中myservice,把它的describedBy属性置为myprocess,presents属性置为myprofile,supports属性置为mygrounding。

7、现在对myprocess进行编辑,OWL-S Editor提供了一个可视化的编辑界面(Visual Editor),利用它可以很方便的定义CompositeProcess的各个步骤。选中myprocess,右边切换到Visual Editor,这里有一些粉红色的按钮用来定制流程。我们首先创建一个Sequence(表示按顺序执行),然后选中这个Sequence,创建两个Perform和一个Produce,每个Perform代表调用一个Web服务,而Produce的作用是在最后得到返回值。这时右边的图形应该像下面这样,因为我们尚未对Perform和Produce进行定制。

file
图3 包含三个有用节点的process图

8、在图形的Perform/Produce节点上点一下就可以修改它的属性,先来修改第一个。点一下第一个矩形节点(第一个Perform),在对话框里把process属性设置为wi1:HashStringProcess(注意:如果导入WSDL时改变了前缀,这里就不是wi1),为了方便阅读,把Name属性改为hashPerform。类似的,第二个矩形节点的process属性应该是wi2:CheckHashProcess,Name则改为checkPerform;对于唯一的Produce节点,改名为produce。现在右边的图如下所示。

file
图4 改名后的process图

9、现在从Visual Editor切换到Properties页,在这里为myprocess定义输入和输出参数。它的输入应该是wi1:HashType和wi1:Str,而输出应该是wi2:CheckHashResult,也就是说,对于我们组装出来的Web服务来说,输入是编码类型和待编码字符串,而输出是验证结果。

10、上面我们定义了myprocess拥有的参数,现在就要用到它们了。切换回Visual Editor,在树型列表里选则第一个Perform(hashPerform),把右边切换到Properties页,现在ToParameter属性里还是空白,我们要把myprocess的输入映射到这个Perform,所以按添加按钮把两个输入参数(wi1:HashType和wi1:  Str)加到ToParameter里。选中它们中的一个,可以看到右边有BindingType选项,缺省为valueSource这一项,就用它即可,在下面的FromPerform下拉框里只有一个选项TheParentPerform,选中它。在最下面的FromParameter里选择和你选择的ToParameter项一样的那个选项(wi1:HashType->wi1:HashType,wi1:Str->wi1:Str)。

file
图5 通过参数传递产生“数据流”

11、对于checkPerform,它有三个输入参数,我们希望HashType和hashPerform具有同样的值,所以它的设置和上一步里对HashType的设置一样;OriginalString的设置和上一步的Str一样;HashString属性是上一步得到的结果,所以FromPerform属性应该是hashPerform,FromParameter属性则是wi1:HashStringResult。

12、对produce的设置很简单,在ToParameter属性里加入我们要的结果wi2:CheckHashResult,FromPerform选checkPerform,FromParameter选wi2:CheckHashResult。现在,myprocess对应的process图如下所示。

file
图6 可以从图中看到服务的结构

13、对myprocess的设置到此就结束了,最困难的部分完成了,剩下的工作都很简单和显而易见。选中mygrounding,在它的hasAtomicProcessGrounding属性里加上wi1:HashStringAtomicProcessGrounding和wi2:CheckHashAtomicProcessGrounding。

14、现在myservice已经可以执行了(myprofile里还可以增加一些信息用来描述这个服务)。现在选中myservice,按下执行按钮,在弹出的对话框里HashType框填MD5,Str框填test(任意字符串都可以),然后按Execute按钮就会看到结果,当然,这个服务不论你输入什么字符串都会得到true值,原因不用我说了吧。

file
图7 执行组装后的服务

搬家前链接:https://www.cnblogs.com/bjzhanghao/archive/2005/06/12/173302.html