Hycz's Blog

Life is a game. Why so serious?

Category Archives: Open Sourse

Cassandra 0.8.0 源码分析——数据类型

一、Introduction

Cassandra中用到了一些可排序的数据类型,也就是放在db.marshal包中的那些类,他们实际上就是将一些基本类型,比如String,BigInteger,Long,ByteBuffer,UUID等,封装起来,最重要的是实现了Comparator接口,让这些封装后的数据类型可以用于排序集(例如SortedSet,TreeSet等),封装的过程中还加入了一些特有的方法。这些类的关系如下图(此外还有个异常类,不太重要):

二、设计特点

首先看看处于顶端的抽象类,AbstractType<T>,这个类虽然是抽象类,但是每次调用它的子类的构造器时总是会调用到它的构造器,这是其中的一个设计特点,先来看一个典型的AbstractType<T>的子类的域和构造器部分:

public class BytesType extends AbstractType<ByteBuffer>
{
    public static final BytesType instance = new BytesType();

    BytesType() {} // singleton
    ...
}

可以看到,这里使用了设计模式中的Singleton Pattern。仅有的那个静态常量是instance,当第一次调用到ByteType时,instance被初始化,实际上,instance成为了唯一的对外实例化ByteType的手段,而且成为了唯一的ByteType实例。这里用到的是传统的Singleton Pattern实现方法,一旦类被初始化,那么就会实例化,更好的是Bill Pugh的方案,不过已经不在本文讨论范围内。然而,可以看到,构造器中并无代码,于是,无疑是其父类的构造器被调用了,也就是AbstractType<T>中的构造器,那么,来看看AbstractType<T>的域和构造器部分:

public abstract class AbstractType<T> implements Comparator<ByteBuffer>
{
    public final Comparator<IndexInfo> indexComparator;
    public final Comparator<IndexInfo> indexReverseComparator;
    public final Comparator<IColumn> columnComparator;
    public final Comparator<IColumn> columnReverseComparator;
    public final Comparator<ByteBuffer> reverseComparator;

    protected AbstractType()
    {
        indexComparator = new Comparator<IndexInfo>()
        {
            public int compare(IndexInfo o1, IndexInfo o2)
            {
                return AbstractType.this.compare(o1.lastName, o2.lastName);
            }
        };
        indexReverseComparator = new Comparator<IndexInfo>()
        {
            public int compare(IndexInfo o1, IndexInfo o2)
            {
                return AbstractType.this.compare(o1.firstName, o2.firstName);
            }
        };
        columnComparator = new Comparator<IColumn>()
        {
            public int compare(IColumn c1, IColumn c2)
            {
                return AbstractType.this.compare(c1.name(), c2.name());
            }
        };
        columnReverseComparator = new Comparator<IColumn>()
        {
            public int compare(IColumn c1, IColumn c2)
            {
                return AbstractType.this.compare(c2.name(), c1.name());
            }
        };
        reverseComparator = new Comparator<ByteBuffer>()
        {
            public int compare(ByteBuffer o1, ByteBuffer o2)
            {
                if (o1.remaining() == 0)
                {
                    return o2.remaining() == 0 ? 0 : -1;
                }
                if (o2.remaining() == 0)
                {
                    return 1;
                }

                return -AbstractType.this.compare(o1, o2);
            }
        };
    }
    ...
}

可以看到,其实这里并没有做很多特别的操作,仅仅是初始化了5个Comparator,以供以后的子类调用,由于这5个对象仅仅实现了一个Comparator接口,所以它们存在的意义也就仅仅是提供一个compare方法,然后就可以作为参数填入。

除了上述的工作,剩下的就是实例化的意义。一旦实例化之后,就相当于得到了一个实现了Comparator接口的实例,于是在很多方法的调用中都可以将其填入。此外,Singleton的意义在于这个实例成为了某种特定的类型的一系列操作工具的入口,而不是什么保存某个特定类型数据的地方。

三、基本类型与封装后类型的关系

这里有一个问题,就是实现的接口Comparator<T>中,为什么填入的是ByteBuffer,这里有一篇关于Java中的Buffer的文章(http://zcdxzsz.iteye.com/blog/310917),ByteBuffer的存在相当于是所有其他基本Buffer的综合,然后在Buffer中以Byte的形式进行操作。然后,Comparator<ByteBuffer>迫使所有的最终子类实现一个方法,作为排序的依据:

    public int compare(ByteBuffer o1, ByteBuffer o2)

实现Comparator<ByteBuffer>的意义也就止于此。至于上面提到的5个Comparator,我的理解是他们没有独特的操作,连写一个子类的价值都没有,所以就只有5个变量而已 。

下一个让人关注的问题就是泛型AbstractType<T>是怎么工作的,自然,T的填入是根据需要封装的类型而决定的,具体来说,是下面几个:String,BigInteger,Long,ByteBuffer,UUID。填入的具体的T有什么作用呢?来看一看AbstractType<T>中是怎么定义的:

    public abstract T compose(ByteBuffer bytes);

    public abstract ByteBuffer decompose(T value);

    /** get a string representation of a particular type. */
    public abstract String toString(T t);

    /** get a string representation of the bytes suitable for log messages */
    public abstract String getString(ByteBuffer bytes);

    /** get a byte representation of the given string.
     *  defaults to unsupportedoperation so people deploying custom Types can update at their leisure. */
    public ByteBuffer fromString(String source) throws MarshalException
    {
        throw new UnsupportedOperationException();
    }

    /* validate that the byte array is a valid sequence for the type we are supposed to be comparing */
    public abstract void validate(ByteBuffer bytes) throws MarshalException;

    /** @deprecated; use reverseComparator field instead */
    public Comparator<ByteBuffer> getReverseComparator()
    {
        return reverseComparator;
    }

    /* convenience method */
    public String getString(Collection<ByteBuffer> names)
    {
        StringBuilder builder = new StringBuilder();
        for (ByteBuffer name : names)
        {
            builder.append(getString(name)).append(",");
        }
        return builder.toString();
    }

    /* convenience method */
    public String getColumnsString(Collection<IColumn> columns)
    {
        StringBuilder builder = new StringBuilder();
        for (IColumn column : columns)
        {
            builder.append(column.getString(this)).append(",");
        }
        return builder.toString();
    }

    public boolean isCommutative()
    {
        return false;
    }

    /** returns the class this AbstractType represents. */
    public abstract Class<T> getType();

    //
    // JDBC metadata
    //

    public abstract boolean isSigned();
    public abstract boolean isCaseSensitive();
    public abstract boolean isCurrency();
    public abstract int getPrecision(T obj);
    public abstract int getScale(T obj);
    public abstract int getJdbcType();
    public abstract boolean needsQuotes();

可见,关于T的重要的是compose()和decompose()方法,涉及到如何被封装的类型如何与ByteBuffer之间进行转换,其他涉及到T的方法,toString(),getType(),getPrecision(),getScale(),都是不甚重要的,甚至有些子类都不会使用getPrecision()和getScale(),估计是历史遗留问题。

于是,看完这些之后,就明白,marshal包中的类的作用就是

  1. 提供ByteBuffer中的compare()方法,使其能在排序集中使用;
  2. 提供被封装的类型与ByteBuffer之间的转换;
  3. 封装原始类型到具体的自定义类型,提供各类型的特有方法。
Advertisements

[转]JLine

原帖地址:http://whitesock.iteye.com/blog/692816

Overview

JLine 是一个用来处理控制台输入的Java类库,目前最新的版本是0.9.94。其官方网址是http://jline.sourceforge.net。在介绍JLine之前,首先还是介绍一下Java 6中的Console类,以便进行对比。

2 Java Console

通过调用System.console()方法可以得到与当前虚拟机对应的Console对象。但是该方法并不保证其返回值一定非null,这取决于底层平台和虚拟机启动的方式:如果是通过交互式的命令行启动,并且标准输入和输出流没有被重定向,那么该方法的返回值通常是非null;如果是被自动启动(例如cron)或者通过Eclipse启动,那么返回值通常为null。

Console类支持的功能有限,其中一个比较有用的功能是以非回显(echo)的方式从控制台读取密码。

3 JLine

JLine不依赖任何core Java以外的类库,但是其不是纯Java的实现。

  • 在Windows平台下,JLine通过自带的.dll文件初始化终端。jline.jar中包含了jline32.dll和jline64.dll,在Windows平台上使用的时候, JLine会自动将其解压缩到临时目录并进行加载。
  • 在Unix或者Max OS X平台下,JLine通过stty命令初始化终端。例如通过调用stty -icanon min 1将控制台设置为character-buffered模式。以及通过调用stty -echo禁止控制台回显。在修改终端的属性之前,JLine会对终端的属性进行备份,然后注册一个ShutdownHook,以便在程序退出时进行恢复。由于JVM在非正常退出时(例如收到SIGKILL信号)不保证ShutdownHook一定会被调用,因此终端的属性可能无法恢复。

JLine使用起来非常简单,jline.jar中一共只有20几个类,源码也不难懂。以下是个简单的例子,其中readLine函数的参数指定了命令行提示符:

ConsoleReader reader = new ConsoleReader();
String line = reader.readLine(">");

3.1 Features

3.1.1 Command History

通过按下键盘的上下箭头键,可以浏览输入的历史数据。此外JLine也支持终端快捷键,例如Ctrl+A, Ctrl+W,Ctrl+K, Ctrl+L等等,使用的时候非常便捷。

可以通过ConsoleReader的setUseHistory(boolean useHistory)方法启用/禁用Command History功能。ConsoleReader的history成员变量负责保存历史数据,默认情况下历史数据只保存在内存中。如果希望将历史数据保存到文件中,那么只需要以File对象作为参数构造History对象,并将该History对象设置到ConsoleReader即可。

3.1.2 Character Masking

ConsoleReader提供了一个readLine(final Character mask) 方法,用来指定character mask。如果参数为null,那么输入的字符正常回显;如果为0,那么不回显;否则回显mask指定的字符。

3.1.3 Tab Completion

JLine中跟自动补全相关的接口是Completor,它有以下几个实现:

  • SimpleCompletor: 对一系列指定的字符串进行自动补全。
  • FileNameCompletor: 类似于bash中的文件名自动补全。
  • ClassNameCompletor: 对classpath中出现的全路径类名进自动补全。
  • NullCompletor: 不进行自动补全。
  • ArgumentCompletor: 为每个属性使用指定的Completor。

以下是个简单的例子:

ConsoleReader reader = new ConsoleReader();
List<Completor> completors = new ArrayList<Completor>();
completors.add(new SimpleCompletor(new String[]{"abc", "def"}));
completors.add(new FileNameCompletor());
completors.add(new ClassNameCompletor());
completors.add(new NullCompletor());
reader.addCompletor(new ArgumentCompletor(completors));
reader.readLine(">");

以上例子中首先在命令行上键入a,然后按下TAB后会自动补全第一个属性abc;然后键入空格,再按下TAB会进行文件名的自动补全;再键入空格和按下TAB后会进行类名的自动补全; 再键入空格和按下TAB后不再有自动补全。需要注意的是,ArgumentCompletor会对命令行上所有索引超过completors长度的属性使用completors中最后一个元素指定的Completor。如果要禁用这个行为,那么将completors的最后一个元素设置为NullCompletor对象。

3.1.4 Custom Keybindings

通过创建 HOME/.jlinebindings.properties文件(或者制定 jline.keybindings 系统变量),可以定制keybindings。

4 Known Issues

JLine最知名的问题莫过于在Windows平台下的Eclipse中启动的程序中调用reader.readLine()方法时总是返回null(正确的行为是等待用户输入)。通过debug, 笔者发现通过设置jline.WindowsTerminal.directConsole属性为false,可以解决返回null的问题,但是感觉还是有些其它的问题。总之,笔者没有发现好的对策,只能work around,即对JLine再进行一层封装,在某些场景下仍然使用原始的基于System.in的流处理。

5 Usage

想必很多项目都会为其应用做一层Application之类的抽象。对应用进行监控的常见方式是使用JMX,JMX从逻辑上可以认为是应用程序的一个Shell。笔者为项目中的Application提供了一套完整的Shell抽象,包括Shell、Console、 Process 和 Command等等。其中JLine是一种Console的具体实现。如果是后台应用,那么可以通过基于Socket的Console连接到应用,从而进行监控,例如停止应用程序等。如果前台的交互式应用,那么可以直接使用基于终端的Console实现, 例如笔者项目中为Spring Batch实现的CommandLine Scheduler等。