package org.nutz.img;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Iterator;

import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;

import org.nutz.lang.Files;
import org.nutz.lang.Lang;
import org.nutz.lang.Streams;
import org.nutz.lang.Strings;
import org.nutz.lang.random.R;
import org.nutz.repo.Base64;

/**
 * 对图像操作的简化 API
 *
 * @author zozoh(zozohtnt@gmail.com)
 */
public class Images {
    /**
     * 对一个图像进行旋转
     *
     * @param srcIm
     *            原图像文件
     * @param taIm
     *            转换后的图像文件
     * @param degree
     *            旋转角度, 90 为顺时针九十度， -90 为逆时针九十度
     * @return 旋转后得图像对象
     */
    public static BufferedImage rotate(Object srcIm, File taIm, int degree) {
        BufferedImage im = Images.read(srcIm);
        BufferedImage im2 = Images.rotate(im, degree);
        Images.write(im2, taIm);
        return im2;
    }

    /**
     * 对一个图像进行旋转
     *
     * @param srcPath
     *            原图像文件路径
     * @param taPath
     *            转换后的图像文件路径
     * @param degree
     *            旋转角度, 90 为顺时针九十度， -90 为逆时针九十度
     * @return 旋转后得图像对象
     */
    public static BufferedImage rotate(String srcPath, String taPath, int degree)
            throws IOException {
        File srcIm = Files.findFile(srcPath);
        if (null == srcIm)
            throw Lang.makeThrow("Fail to find image file '%s'!", srcPath);

        File taIm = Files.createFileIfNoExists(taPath);
        return rotate(srcIm, taIm, degree);
    }

    /**
     * 对一个图像进行旋转
     *
     * @param image
     *            图像
     * @param degree
     *            旋转角度, 90 为顺时针九十度， -90 为逆时针九十度
     * @return 旋转后得图像对象
     */
    public static BufferedImage rotate(BufferedImage image, int degree) {
        int iw = image.getWidth();// 原始图象的宽度
        int ih = image.getHeight();// 原始图象的高度
        int w = 0;
        int h = 0;
        int x = 0;
        int y = 0;
        degree = degree % 360;
        if (degree < 0)
            degree = 360 + degree;// 将角度转换到0-360度之间
        double ang = degree * 0.0174532925;// 将角度转为弧度

        /**
         * 确定旋转后的图象的高度和宽度
         */

        if (degree == 180 || degree == 0 || degree == 360) {
            w = iw;
            h = ih;
        } else if (degree == 90 || degree == 270) {
            w = ih;
            h = iw;
        } else {
            int d = iw + ih;
            w = (int) (d * Math.abs(Math.cos(ang)));
            h = (int) (d * Math.abs(Math.sin(ang)));
        }

        x = (w / 2) - (iw / 2);// 确定原点坐标
        y = (h / 2) - (ih / 2);
        BufferedImage rotatedImage = new BufferedImage(w, h, image.getType());
        Graphics2D gs = rotatedImage.createGraphics();
        gs.fillRect(0, 0, w, h);// 以给定颜色绘制旋转后图片的背景
        AffineTransform at = new AffineTransform();
        at.rotate(ang, w / 2, h / 2);// 旋转图象
        at.translate(x, y);
        AffineTransformOp op = new AffineTransformOp(at, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
        op.filter(image, rotatedImage);
        image = rotatedImage;
        return image;
    }

    /**
     * 自动等比缩放一个图片，并将其保存成目标图像文件

     * 多余的部分，用给定背景颜色补上

     * 如果参数中的宽度或高度为-1的话，着按照指定的高度或宽度对原图等比例缩放图片，不添加背景颜色
     *

     * 图片格式支持 png | gif | jpg | bmp | wbmp
     *
     * @param srcIm
     *            源图像文件对象
     * @param taIm
     *            目标图像文件对象
     * @param w
     *            宽度
     * @param h
     *            高度
     * @param bgColor
     *            背景颜色
     *
     * @return 被转换前的图像对象
     *
     * @throws IOException
     *             当读写文件失败时抛出
     */
    public static BufferedImage zoomScale(Object srcIm, File taIm, int w, int h, Color bgColor)
            throws IOException {
        BufferedImage old = read(srcIm);
        BufferedImage im = Images.zoomScale(old, w, h, bgColor);
        write(im, taIm);
        return old;
    }

    /**
     * 自动等比缩放一个图片，并将其保存成目标图像文件

     * 多余的部分，用给定背景颜色补上

     * 如果参数中的宽度或高度为-1的话，着按照指定的高度或宽度对原图等比例缩放图片，不添加背景颜色
     *

     * 图片格式支持 png | gif | jpg | bmp | wbmp
     *
     * @param srcPath
     *            源图像路径
     * @param taPath
     *            目标图像路径，如果不存在，则创建
     * @param w
     *            宽度
     * @param h
     *            高度
     * @param bgColor
     *            背景颜色
     *
     * @return 被转换前的图像对象
     *
     * @throws IOException
     *             当读写文件失败时抛出
     */
    public static BufferedImage zoomScale(String srcPath,
                                          String taPath,
                                          int w,
                                          int h,
                                          Color bgColor)
            throws IOException {
        File srcIm = Files.findFile(srcPath);
        if (null == srcIm)
            throw Lang.makeThrow("Fail to find image file '%s'!", srcPath);

        File taIm = Files.createFileIfNoExists(taPath);
        return zoomScale(srcIm, taIm, w, h, bgColor);
    }

    /**
     * 自动等比缩放一个图片，多余的部分，用给定背景颜色补上

     * 如果参数中的宽度或高度为-1的话，着按照指定的高度或宽度对原图等比例缩放图片，不添加背景颜色
     *
     * @param im
     *            图像对象
     * @param w
     *            宽度
     * @param h
     *            高度
     * @param bgColor
     *            背景颜色
     *
     * @return 被转换后的图像
     */
    public static BufferedImage zoomScale(BufferedImage im, int w, int h, Color bgColor) {
        if (w == -1 || h == -1) {
            return scale(im, w, h);
        }

        // 检查背景颜色
        // bgColor = null == bgColor ? Color.black : bgColor;
        // 获得尺寸
        int oW = im.getWidth();
        int oH = im.getHeight();
        float oR = (float) oW / (float) oH;
        float nR = (float) w / (float) h;

        int nW, nH, x, y;
        /*
         * 缩放
         */
        // 原图太宽，计算当原图与画布同高时，原图的等比宽度
        if (oR > nR) {
            nW = w;
            nH = (int) ((w) / oR);
            x = 0;
            y = (h - nH) / 2;
        }
        // 原图太高
        else if (oR < nR) {
            nH = h;
            nW = (int) ((h) * oR);
            x = (w - nW) / 2;
            y = 0;
        }
        // 比例相同
        else {
            nW = w;
            nH = h;
            x = 0;
            y = 0;
        }

        // 创建图像
        BufferedImage re = new BufferedImage(w, h, im.getType() == 0 ? BufferedImage.TYPE_3BYTE_BGR : im.getType());
        Graphics2D gc = re.createGraphics();
        if (null != bgColor) {
            gc.setColor(bgColor);
            gc.fillRect(0, 0, w, h);
        }

        // 绘制图像
        gc.drawImage(im, x, y, nW, nH, bgColor, null);

        // 释放
        gc.dispose();

        // 返回
        return re;
    }

    /**
     * @see #zoomScale(BufferedImage, int, int, Color)
     */
    public static BufferedImage zoomScale(BufferedImage im, int w, int h) {
        return zoomScale(im, w, h, null);
    }

    /**
     * 自动等比缩放一个图片
     *
     * @param im
     *            图像对象
     * @param w
     *            宽度
     * @param h
     *            高度
     *
     * @return 被转换后的图像
     */
    public static BufferedImage scale(BufferedImage im, int w, int h) {
        // 获得尺寸
        int oW = im.getWidth();
        int oH = im.getHeight();

        int nW = w, nH = h;

        /*
         * 缩放
         */
        // 未指定图像高度，根据原图尺寸计算出高度
        if (h == -1) {
            nH = (int) ((float) w / oW * oH);
        }
        // 未指定图像宽度，根据原图尺寸计算出宽度
        else if (w == -1) {
            nW = (int) ((float) h / oH * oW);
        }

        // 创建图像
        BufferedImage re = new BufferedImage(nW, nH, im.getType());
        Graphics2D gc = re.createGraphics();
        gc.drawImage(im, 0, 0, nW, nH, null);
        gc.dispose();
        // 返回
        return re;
    }

    /**
     * 自动缩放剪切一个图片，令其符合给定的尺寸，并将其保存成目标图像文件
     *

     * 图片格式支持 png | gif | jpg | bmp | wbmp
     *
     * @param srcIm
     *            源图像文件对象
     * @param taIm
     *            目标图像文件对象
     * @param w
     *            宽度
     * @param h
     *            高度
     * @return 被转换前的图像对象
     *
     * @throws IOException
     *             当读写文件失败时抛出
     */
    public static BufferedImage clipScale(Object srcIm, File taIm, int w, int h)
            throws IOException {
        BufferedImage old = read(srcIm);
        BufferedImage im = Images.clipScale(old, w, h);
        write(im, taIm);
        return old;
    }

    /**
     * 自动缩放剪切一个图片，令其符合给定的尺寸，并将其保存到目标图像路径
     *

     * 图片格式支持 png | gif | jpg | bmp | wbmp
     *
     * @param srcPath
     *            源图像路径
     * @param taPath
     *            目标图像路径，如果不存在，则创建
     * @param w
     *            宽度
     * @param h
     *            高度
     *
     * @return 被转换前的图像对象
     *
     * @throws IOException
     *             当读写文件失败时抛出
     */
    public static BufferedImage clipScale(String srcPath, String taPath, int w, int h)
            throws IOException {
        File srcIm = Files.findFile(srcPath);
        if (null == srcIm)
            throw Lang.makeThrow("Fail to find image file '%s'!", srcPath);

        File taIm = Files.createFileIfNoExists(taPath);
        return clipScale(srcIm, taIm, w, h);
    }

    public static BufferedImage clipScale(Object srcIm, int[] startPoint, int[] endPoint) {
        // 计算给定坐标后的图片的尺寸
        int width = endPoint[0] - startPoint[0];
        int height = endPoint[1] - startPoint[1];

        BufferedImage old = read(srcIm);
        BufferedImage im = Images.clipScale(old.getSubimage(startPoint[0],
                                                            startPoint[1],
                                                            width,
                                                            height),
                                            width,
                                            height);
        return im;
    }

    /**
     * 根据给定的起始坐标点与结束坐标点来剪切一个图片，令其符合给定的尺寸，并将其保存成目标图像文件
     *

     * 图片格式支持 png | gif | jpg | bmp | wbmp
     *
     * @param srcIm
     *            源图像文件对象
     * @param taIm
     *            目标图像文件对象
     * @param startPoint
     *            起始坐标点，其值[x, y]为相对原图片左上角的坐标
     * @param endPoint
     *            结束坐标点，其值[x, y]为相对原图片左上角的坐标
     * @return 被转换前的图像对象
     *
     * @throws IOException
     *             当读写文件失败时抛出
     */
    public static BufferedImage clipScale(Object srcIm, File taIm, int[] startPoint, int[] endPoint)
            throws IOException {
        BufferedImage old = read(srcIm);
        BufferedImage im = clipScale(old, startPoint, endPoint);
        write(im, taIm);
        return old;
    }

    /**
     * 根据给定的起始坐标点与结束坐标点来剪切一个图片，令其符合给定的尺寸，并将其保存成目标图像文件
     *

     * 图片格式支持 png | gif | jpg | bmp | wbmp
     *
     * @param srcPath
     *            源图像文件对象
     * @param taPath
     *            目标图像文件对象
     * @param startPoint
     *            起始坐标点，其值[x, y]为相对原图片左上角的坐标
     * @param endPoint
     *            结束坐标点，其值[x, y]为相对原图片左上角的坐标
     * @return 被转换前的图像对象
     *
     * @throws IOException
     *             当读写文件失败时抛出
     */
    public static BufferedImage clipScale(String srcPath,
                                          String taPath,
                                          int[] startPoint,
                                          int[] endPoint)
            throws IOException {
        File srcIm = Files.findFile(srcPath);
        if (null == srcIm)
            throw Lang.makeThrow("Fail to find image file '%s'!", srcPath);

        File taIm = Files.createFileIfNoExists(taPath);
        return clipScale(srcIm, taIm, startPoint, endPoint);
    }

    /**
     * 自动缩放剪切一个图片，令其符合给定的尺寸
     *

     * 如果图片太大，则将其缩小，如果图片太小，则将其放大，多余的部分被裁减
     *
     * @param im
     *            图像对象
     * @param w
     *            宽度
     * @param h
     *            高度
     * @return 被转换后的图像
     */
    public static BufferedImage clipScale(BufferedImage im, int w, int h) {
        // 获得尺寸
        int oW = im.getWidth();
        int oH = im.getHeight();
        float oR = (float) oW / (float) oH;
        float nR = (float) w / (float) h;

        int nW, nH, x, y;
        /*
         * 裁减
         */
        // 原图太宽，计算当原图与画布同高时，原图的等比宽度
        if (oR > nR) {
            nW = (h * oW) / oH;
            nH = h;
            x = (w - nW) / 2;
            y = 0;
        }
        // 原图太长
        else if (oR < nR) {
            nW = w;
            nH = (w * oH) / oW;
            x = 0;
            y = (h - nH) / 2;
        }
        // 比例相同
        else {
            nW = w;
            nH = h;
            x = 0;
            y = 0;
        }
        // 创建图像
        BufferedImage re = new BufferedImage(w, h, ColorSpace.TYPE_RGB);
        re.createGraphics().drawImage(im, x, y, nW, nH, Color.black, null);
        // 返回
        return re;
    }

    /**
     * 水平翻转一张图片
     *
     * @param srcIm
     *            源图片
     * @return 被转换后的图像
     */
    public static BufferedImage flipHorizontal(Object srcIm) {
        BufferedImage im1 = read(srcIm);
        int w = im1.getWidth();
        int h = im1.getHeight();
        BufferedImage flipImage = new BufferedImage(w, h, im1.getType());
        Graphics2D gs = flipImage.createGraphics();
        gs.drawImage(im1, 0, 0, w, h, w, 0, 0, h, null);
        gs.dispose();
        return flipImage;
    }

    /**
     * 水平翻转一张图片
     *
     * @param srcIm
     *            源图片
     * @param tarIm
     *            目标图片
     * @return 被转换后的图像
     */
    public static BufferedImage flipHorizontal(Object srcIm, File tarIm) {
        BufferedImage flipImage = flipHorizontal(srcIm);
        Images.write(flipImage, tarIm);
        return flipImage;
    }

    /**
     * 垂直翻转一张图片
     *
     * @param srcIm
     *            源图片
     * @return 被转换后的图像
     */
    public static BufferedImage flipVertical(Object srcIm) {
        BufferedImage im1 = read(srcIm);
        int w = im1.getWidth();
        int h = im1.getHeight();
        BufferedImage flipImage = new BufferedImage(w, h, im1.getType());
        Graphics2D gs = flipImage.createGraphics();
        gs.drawImage(im1, 0, 0, w, h, 0, h, w, 0, null);
        gs.dispose();
        return flipImage;
    }

    /**
     * 垂直翻转一张图片
     *
     * @param srcIm
     *            源图片
     * @param tarIm
     *            目标图片
     * @return 被转换后的图像
     */
    public static BufferedImage flipVertical(Object srcIm, File tarIm) {
        BufferedImage flipImage = flipVertical(srcIm);
        Images.write(flipImage, tarIm);
        return flipImage;
    }

    /**
     * 扭曲图片
     *
     * @param srcIm
     *            源图片
     * @param twistRank
     *            扭曲程度，默认为1，数值越大扭曲程度越高
     * @param bgColor
     *            扭曲后露出的底图填充色，一般选择要源图片的背景色
     * @return 被扭曲后的图片
     */
    public static BufferedImage twist(Object srcIm, double twistRank, String bgColor) {
        if (twistRank <= 0) {
            twistRank = 1;
        }
        BufferedImage bufImg = read(srcIm);
        double period = R.random(0, 7) + 3;// 波形的幅度倍数，越大扭曲的程序越高，一般为3
        double phase = R.random(0, 6);// 波形的起始相位，取值区间（0-2＊PI）
        int width = bufImg.getWidth();
        int height = bufImg.getHeight();

        BufferedImage tarIm = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D gc = tarIm.createGraphics();
        gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        gc.setBackground(Strings.isBlank(bgColor) ? Colors.randomColor() : Colors.as(bgColor));
        gc.clearRect(0, 0, width, height);

        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                int nX = pos4twist(twistRank, phase, period, height, i, j);
                int nY = j;
                if (nX >= 0 && nX < width && nY >= 0 && nY < height) {
                    tarIm.setRGB(nX, nY, bufImg.getRGB(i, j));
                }
            }
        }
        return tarIm;
    }

    // 扭曲相关计算, 后面的参数有两种组合
    // 1. height, x, y
    // 2. width, y, x
    private static int pos4twist(double rank,
                                 double phase,
                                 double period,
                                 int hOrW,
                                 int xOrY,
                                 int yOrX) {
        double dyOrX = Math.PI * rank * yOrX / hOrW + phase;
        double dxOrY = Math.sin(dyOrX);
        return xOrY + (int) (dxOrY * period);
    }

    public static final int WATERMARK_TOP_LEFT = 1;
    public static final int WATERMARK_TOP_CENTER = 2;
    public static final int WATERMARK_TOP_RIGHT = 3;
    public static final int WATERMARK_CENTER_LEFT = 4;
    public static final int WATERMARK_CENTER = 5;
    public static final int WATERMARK_CENTER_RIGHT = 6;
    public static final int WATERMARK_BOTTOM_LEFT = 7;
    public static final int WATERMARK_BOTTOM_CENTER = 8;
    public static final int WATERMARK_BOTTOM_RIGHT = 9;

    /**
     * 为图片添加水印，可以设定透明度与水印的位置
     *

     * 水印位置默认支持9种，分别是：
     *
     * TOP_LEFT | TOP_CENTER | TOP_RIGHT CENTER_LEFT | CENTER | CENTER_RIGHT
     * BOTTOM_LEFT | BOTTOM_CENTER | BOTTOM_RIGHT
     *
     *
     * @param srcIm
     *            源图片
     * @param markIm
     *            水印图片
     * @param opacity
     *            透明度, 要求大于0小于1, 默认为0.5f
     * @param pos
     *            共9个位置，请使用 Images.WATERMARK_{XXX} 进行设置，默认为
     *            Images.WATERMARK_CENTER
     * @param margin
     *            水印距离四周的边距 默认为0
     * @return
     */
    public static BufferedImage addWatermark(Object srcIm,
                                             Object markIm,
                                             float opacity,
                                             int pos,
                                             int margin) {
        BufferedImage im1 = read(srcIm);
        BufferedImage im2 = read(markIm);

        int cw = im1.getWidth();
        int ch = im1.getHeight();
        int mw = im2.getWidth();
        int mh = im2.getHeight();

        if (opacity > 1 || opacity <= 0) {
            opacity = 0.5f;
        }
        if (pos > 9 || pos <= 0) {
            pos = 5;
        }

        // 计算水印位置
        int px = 0;
        int py = 0;
        switch (pos) {
        case WATERMARK_TOP_LEFT:
            px = margin;
            py = margin;
            break;
        case WATERMARK_TOP_CENTER:
            px = (cw - mw) / 2;
            py = margin;
            break;
        case WATERMARK_TOP_RIGHT:
            px = cw - mw - margin;
            py = margin;
            break;
        case WATERMARK_CENTER_LEFT:
            px = margin;
            py = (ch - mh) / 2;
            break;
        case WATERMARK_CENTER:
            px = (cw - mw) / 2;
            py = (ch - mh) / 2;
            break;
        case WATERMARK_CENTER_RIGHT:
            px = cw - mw - margin;
            py = (ch - mh) / 2;
            break;
        case WATERMARK_BOTTOM_LEFT:
            px = margin;
            py = ch - mh - margin;
            break;
        case WATERMARK_BOTTOM_CENTER:
            px = (cw - mw) / 2;
            py = ch - mh - margin;
            break;
        case WATERMARK_BOTTOM_RIGHT:
            px = cw - mw - margin;
            py = ch - mh - margin;
            break;
        }

        // 添加水印
        Graphics2D gs = im1.createGraphics();
        gs.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, opacity));
        gs.drawImage(im2, px, py, null);
        gs.dispose();

        return im1;
    }

    /**
     * 获取灰度图像
     *
     * @param srcIm
     *            源图片
     * @return 灰度图片
     */
    public static BufferedImage grayImage(Object srcIm) {
        BufferedImage srcImage = read(srcIm);
        BufferedImage grayImage = new BufferedImage(srcImage.getWidth(),
                                                    srcImage.getHeight(),
                                                    srcImage.getType());
        for (int i = 0; i < srcImage.getWidth(); i++) {
            for (int j = 0; j < srcImage.getHeight(); j++) {
                grayImage.setRGB(i, j, Colors.getGray(srcImage, i, j));
            }
        }
        return grayImage;
    }

    /**
     * 实现两张图片的正片叠底效果
     *
     * @param bgIm
     *            背景图
     * @param itemIm
     *            上层图
     * @param x
     *            上层图横坐标
     * @param y
     *            上层图横坐标
     * @return 正片叠底后的图片
     */
    public static BufferedImage multiply(Object bgIm, Object itemIm, int x, int y) {

        BufferedImage viewportImage = read(bgIm);
        BufferedImage itemImage = read(itemIm);
        BufferedImage muImage = new BufferedImage(viewportImage.getWidth(),
                                                  viewportImage.getHeight(),
                                                  viewportImage.getType());
        // 背景图为视口范围，上层图不能超过视口进行绘制, 只有重合部分进行计算叠底
        int xMin = x;
        int xMax = x + itemImage.getWidth();
        int yMin = y;
        int yMax = y + itemImage.getHeight();
        for (int i = 0; i < viewportImage.getWidth(); i++) {
            for (int j = 0; j < viewportImage.getHeight(); j++) {
                int rgb = 0;
                // 判断是否重合
                if (i >= xMin && i < xMax && j >= yMin && j < yMax) {
                    // 获取两个图rgb值
                    int vpRGB = viewportImage.getRGB(i, j);
                    int imRGB = itemImage.getRGB(i - x, j - y);
                    rgb = Colors.getMultiply(vpRGB, imRGB);
                } else {
                    rgb = viewportImage.getRGB(i, j);
                }
                muImage.setRGB(i, j, rgb);
            }
        }

        return muImage;
    }

    /**
     * 根据亮度值（灰度值）来自动计算哪些像素需要扣掉。
     *
     *

     *
     * 适合前后亮度差别特别明显的图片，比如背景全黑。
     *
     * @param srcIm
     *            源图片
     * @return 抠图后图片对象
     */
    public static BufferedImage cutoutByLuminance(Object srcIm) {
        return cutoutByChannel(srcIm, -1);
    }

    /**
     * 根据指定通道的亮度值（灰度值）来自动计算哪些像素需要扣掉。
     *
     * @param srcIm
     *            源图片
     * @param channel
     *            通道编号，0:red 1:green 2:blue 其他:亮度
     * @return 抠图后图片对象
     */
    public static BufferedImage cutoutByChannel(Object srcIm, int channel) {
        BufferedImage srcImage = read(srcIm);
        BufferedImage resultImage = new BufferedImage(srcImage.getWidth(),
                                                      srcImage.getHeight(),
                                                      BufferedImage.TYPE_4BYTE_ABGR);
        // 开始绘制
        for (int i = 0; i < srcImage.getWidth(); i++) {
            for (int j = 0; j < srcImage.getHeight(); j++) {
                int pixel = srcImage.getRGB(i, j);
                int alpha = 0;
                switch (channel) {
                case CHANNEL_RED:
                    alpha = Colors.getRGB(pixel)[0];
                    break;
                case CHANNEL_GREEN:
                    alpha = Colors.getRGB(pixel)[1];
                    break;
                case CHANNEL_BLUE:
                    alpha = Colors.getRGB(pixel)[2];
                    break;
                default:
                    alpha = Colors.getLuminance(srcImage, i, j);
                    break;
                }
                pixel = (alpha << 24) & 0xff000000 | (pixel & 0x00ffffff);
                resultImage.setRGB(i, j, pixel);
            }
        }

        return resultImage;
    }

    /**
     * 指定的像素点为背景色参考，在指定范围内的颜色将设置为透明。
     *
     *

     * ！！！ 该方法适合背景与前景相差特别大的图片，最好是背景颜色基本一致，前景背景有明显分隔界限。
     *
     *
     * @param srcIm
     *            源图片
     * @param x
     *            采样像素点横坐标
     * @param y
     *            采样像素点纵坐标
     * @param range
     *            采样像素可允许色差范围，数值越大去掉的颜色范围越多
     * @return 抠图后图片对象
     *
     */
    public static BufferedImage cutoutByPixel(Object srcIm, int x, int y, int range) {
        BufferedImage srcImage = read(srcIm);
        BufferedImage resultImage = new BufferedImage(srcImage.getWidth(),
                                                      srcImage.getHeight(),
                                                      BufferedImage.TYPE_4BYTE_ABGR);
        // 获取选样点
        int[] srgb = Colors.getRGB(srcImage.getRGB(x, y));

        // 开始绘制
        for (int i = 0; i < srcImage.getWidth(); i++) {
            for (int j = 0; j < srcImage.getHeight(); j++) {
                int pixel = srcImage.getRGB(i, j);
                int[] crgb = Colors.getRGB(pixel);
                int alpha = 255;
                // 范围内的都干掉
                if (inRangeColor(srgb, crgb, range)) {
                    alpha = 0;
                }
                // 范围大一点点的，可能就需要半透明来处理了
                else if (inRangeColor(srgb, crgb, (int) (range * 1.5))) {
                    alpha = 64;
                }
                // 范围大一点点的，可能就需要半透明来处理了
                else if (inRangeColor(srgb, crgb, range * 2)) {
                    alpha = 128;
                }
                // 不在范围的原样输出吧
                else {
                    alpha = Colors.getAlpha(pixel);
                }
                pixel = (alpha << 24) & 0xff000000 | (pixel & 0x00ffffff);
                resultImage.setRGB(i, j, pixel);
            }
        }

        return resultImage;
    }

    private static boolean inRangeColor(int[] srgb, int[] crgb, int range) {
        // r
        if (crgb[0] >= srgb[0] - range && crgb[0] <= srgb[0] + range) {
            // g
            if (crgb[1] >= srgb[01] - range && crgb[1] <= srgb[1] + range) {
                // b
                if (crgb[2] >= srgb[2] - range && crgb[2] <= srgb[2] + range) {
                    return true;
                }
            }
        }
        return false;
    }

    public static final int CHANNEL_RED = 0;
    public static final int CHANNEL_GREEN = 1;
    public static final int CHANNEL_BLUE = 2;

    /**
     * 获取三原色通道图片
     *
     * @param srcIm
     *            源图片
     * @param channel
     *            通道编号，0:red 1:green 2:blue
     * @return 单一通道图片
     */
    public static BufferedImage channelImage(Object srcIm, int channel) {
        BufferedImage srcImage = read(srcIm);
        BufferedImage rcImage = new BufferedImage(srcImage.getWidth(),
                                                  srcImage.getHeight(),
                                                  srcImage.getType());
        for (int i = 0; i < srcImage.getWidth(); i++) {
            for (int j = 0; j < srcImage.getHeight(); j++) {
                int r = Colors.getRGB(srcImage, i, j)[channel];
                rcImage.setRGB(i, j, new Color(r, r, r).getRGB());
            }
        }
        return rcImage;
    }

    /**
     * 将一个图片文件读入内存
     *
     * @param img
     *            图片文件
     * @return 图片对象
     */
    public static BufferedImage read(Object img) {
        try {
            if (img instanceof BufferedImage) {
                return (BufferedImage) img;
            }
            if (img instanceof CharSequence) {
                return ImageIO.read(Files.checkFile(img.toString()));
            }
            if (img instanceof File)
                return ImageIO.read((File) img);

            if(img instanceof byte[])
                return ImageIO.read(new ByteArrayInputStream((byte[])img));

            if (img instanceof URL)
                img = ((URL) img).openStream();

            if (img instanceof InputStream) {
                File tmp = File.createTempFile("nutz_img", ".jpg");
                Files.write(tmp, img);
                try {
                    return read(tmp);
                }
                finally {
                    tmp.delete();
                }
            }
            throw Lang.makeThrow("Unkown img info!! --> " + img);
        }
        catch (IOException e) {
            try {
                InputStream in = null;
                if (img instanceof File)
                    in = new FileInputStream((File) img);
                else if (img instanceof URL)
                    in = ((URL) img).openStream();
                else if (img instanceof InputStream)
                    in = (InputStream) img;
                if (in != null)
                    return readJpeg(in);
            }
            catch (IOException e2) {
                e2.fillInStackTrace();
            }
            return null;
            // throw Lang.wrapThrow(e);
        }
    }

    /**
     * 将内存中一个图片写入目标文件
     *
     * @param im
     *            图片对象
     * @param targetFile
     *            目标文件，根据其后缀，来决定写入何种图片格式
     */
    public static void write(RenderedImage im, File targetFile) {
        try {
            ImageIO.write(im, Files.getSuffixName(targetFile), targetFile);
        }
        catch (IOException e) {
            throw Lang.wrapThrow(e);
        }
    }

    /**
     * 将内存中的一个写入输出流
     *
     * @param im
     *            图片对象
     * @param imFormat
     *            图片格式
     * @param out
     *            输出流
     */
    public static void write(RenderedImage im, String imFormat, OutputStream out) {
        try {
            ImageIO.write(im, imFormat, out);
        }
        catch (IOException e) {
            throw Lang.wrapThrow(e);
        }
    }

    /**
     * @see #write(RenderedImage, String, OutputStream)
     */
    public static void writeAndClose(RenderedImage im, String imFormat, OutputStream out) {
        try {
            ImageIO.write(im, imFormat, out);
        }
        catch (IOException e) {
            throw Lang.wrapThrow(e);
        }
        finally {
            Streams.safeClose(out);
        }
    }

    /**
     * 写入一个 JPG 图像
     *
     * @param im
     *            图像对象
     * @param targetJpg
     *            目标输出 JPG 图像文件
     * @param quality
     *            质量 0.1f ~ 1.0f
     */
    public static void writeJpeg(RenderedImage im, Object targetJpg, float quality) {
        ImageWriter writer = null;
        try {
            writer = ImageIO.getImageWritersBySuffix("jpg").next();
            ImageWriteParam param = writer.getDefaultWriteParam();
            param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
            param.setCompressionQuality(quality);
            ImageOutputStream os = ImageIO.createImageOutputStream(targetJpg);
            writer.setOutput(os);
            writer.write((IIOMetadata) null, new IIOImage(im, null, null), param);
            os.flush();
            os.close();
        }
        catch (IOException e) {
            throw Lang.wrapThrow(e);
        }
        finally {
            if (writer != null) {
                try {
                    writer.dispose();
                }
                catch (Throwable e) {}
            }
        }
    }

    /**
     * 尝试读取JPEG文件的高级方法,可读取32位的jpeg文件
     *

     * 来自:
     * http://stackoverflow.com/questions/2408613/problem-reading-jpeg-image-
     * using-imageio-readfile-file
     *
     */
    private static BufferedImage readJpeg(InputStream in) throws IOException {
        Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("JPEG");
        ImageReader reader = null;
        while (readers.hasNext()) {
            reader = readers.next();
            if (reader.canReadRaster()) {
                break;
            }
        }
        if (reader == null)
            return null;
        try {
            ImageInputStream input = ImageIO.createImageInputStream(in);
            reader.setInput(input);
            // Read the image raster
            Raster raster = reader.readRaster(0, null);
            BufferedImage image = createJPEG4(raster);
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            writeJpeg(image, out, 1);
            out.flush();
            return read(new ByteArrayInputStream(out.toByteArray()));
        }
        finally {
            try {
                reader.dispose();
            }
            catch (Throwable e) {}
        }
    }

    /**
     * Java's ImageIO can't process 4-component images and Java2D can't apply
     * AffineTransformOp either, so convert raster data to RGB. Technique due to
     * MArk Stephens. Free for any use.
     */
    private static BufferedImage createJPEG4(Raster raster) {
        int w = raster.getWidth();
        int h = raster.getHeight();
        byte[] rgb = new byte[w * h * 3];

        float[] Y = raster.getSamples(0, 0, w, h, 0, (float[]) null);
        float[] Cb = raster.getSamples(0, 0, w, h, 1, (float[]) null);
        float[] Cr = raster.getSamples(0, 0, w, h, 2, (float[]) null);
        float[] K = raster.getSamples(0, 0, w, h, 3, (float[]) null);

        for (int i = 0, imax = Y.length, base = 0; i < imax; i++, base += 3) {
            float k = 220 - K[i], y = 255 - Y[i], cb = 255 - Cb[i], cr = 255 - Cr[i];

            double val = y + 1.402 * (cr - 128) - k;
            val = (val - 128) * .65f + 128;
            rgb[base] = val < 0.0 ? (byte) 0 : val > 255.0 ? (byte) 0xff : (byte) (val + 0.5);

            val = y - 0.34414 * (cb - 128) - 0.71414 * (cr - 128) - k;
            val = (val - 128) * .65f + 128;
            rgb[base + 1] = val < 0.0 ? (byte) 0 : val > 255.0 ? (byte) 0xff : (byte) (val + 0.5);

            val = y + 1.772 * (cb - 128) - k;
            val = (val - 128) * .65f + 128;
            rgb[base + 2] = val < 0.0 ? (byte) 0 : val > 255.0 ? (byte) 0xff : (byte) (val + 0.5);
        }

        raster = Raster.createInterleavedRaster(new DataBufferByte(rgb, rgb.length),
                                                w,
                                                h,
                                                w * 3,
                                                3,
                                                new int[]{0, 1, 2},
                                                null);

        ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
        ColorModel cm = new ComponentColorModel(cs,
                                                false,
                                                true,
                                                Transparency.OPAQUE,
                                                DataBuffer.TYPE_BYTE);
        return new BufferedImage(cm, (WritableRaster) raster, true, null);
    }

    /**
     * 生成该图片对应的 Base64 编码的字符串
     *
     * @param targetFile
     *            图片文件
     * @return 图片对应的 Base64 编码的字符串
     */
    public static String encodeBase64(String targetFile) {
        return encodeBase64(new File(targetFile));
    }

    /**
     * 生成该图片对应的 Base64 编码的字符串
     *
     * @param targetFile
     *            图片文件
     * @return 图片对应的 Base64 编码的字符串
     */
    public static String encodeBase64(File targetFile) {
        BufferedImage image = null;

        try {
            image = ImageIO.read(targetFile);
        }
        catch (IOException e) {
            throw Lang.wrapThrow(e);
        }

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(baos);
        image.flush();
        try {
            ImageIO.write(image, Files.getSuffixName(targetFile), bos);
            bos.flush();
            bos.close();
        }
        catch (IOException e) {
            throw Lang.wrapThrow(e);
        }

        byte[] bImage = baos.toByteArray();

        return Base64.encodeToString(bImage, false);
    }

    /**
     * 在一个RGB画布上重新绘制Image,解决CMYK图像偏色的问题
     */
    public static BufferedImage redraw(BufferedImage img, Color bg) {
        BufferedImage rgbImage = new BufferedImage(img.getWidth(),
                                                   img.getHeight(),
                                                   BufferedImage.TYPE_3BYTE_BGR);
        Graphics2D g2d = rgbImage.createGraphics();
        g2d.drawImage(img, 0, 0, bg, null);
        g2d.dispose();
        return rgbImage;
    }

    /**
     * 文字生成图片，黑底白字。
     *
     * @param content
     *            文字内容
     * @return 图像
     */
    public static BufferedImage createText(String content) {
        return createText(content, 0, 0, null, null, null, 0, Font.PLAIN);
    }

    /**
     * 文字生成图片
     *
     * @param content
     *            文字内容
     * @param width
     *            图片宽度，默认256
     * @param height
     *            图片高度，默认256
     * @param fontColor
     *            文字颜色 默认白色
     * @param bgColor
     *            背景颜色 默认黑色
     * @param fontName
     *            字体名称 需运行环境中已有该字体名称
     * @param fontSize
     *            字体大小
     * @param fontStyle
     *            字体样式 Font.PLAIN || Font.BOLD || Font.ITALIC
     * @return 图像
     */
    public static BufferedImage createText(String content,
                                           int width,
                                           int height,
                                           String fontColor,
                                           String bgColor,
                                           String fontName,
                                           int fontSize,
                                           int fontStyle) {
        // 处理下参数
        if (Strings.isBlank(content)) {
            return null;
        }
        if (width <= 0) {
            width = 256;
        }
        if (height <= 0) {
            height = 256;
        }
        if (Strings.isBlank(fontColor)) {
            fontColor = "#FFF";
        }
        if (Strings.isBlank(bgColor)) {
            bgColor = "#000";
        }
        if (fontSize <= 0) {
            fontSize = height / 2;
        }
        if (fontStyle < 0 || fontStyle > 2) {
            fontStyle = Font.BOLD;
        }
        // 准备
        BufferedImage im;
        Graphics2D gc;
        Color colorFont = Colors.as(fontColor);
        Color colorBg = Colors.as(bgColor);
        // 判断图片格式
        int imageType = BufferedImage.TYPE_INT_RGB;
        if (colorFont.getAlpha() < 255 || colorBg.getAlpha() < 255) {
            imageType = BufferedImage.TYPE_INT_ARGB;
        }
        // 生成背景
        im = new BufferedImage(width, height, imageType);
        gc = im.createGraphics();
        gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        gc.setBackground(colorBg);
        gc.clearRect(0, 0, width, height);
        // 写入文字
        Font cFont = Fonts.get(fontName, fontStyle, fontSize);
        gc.setColor(colorFont);
        gc.setFont(cFont);
        FontMetrics cFontM = gc.getFontMetrics(cFont);
        int cW = cFontM.stringWidth(content);
        int ascent = cFontM.getAscent(); // 取得Ascent
        int descent = cFontM.getDescent(); // 取得Descent
        int x, y;
        x = width / 2 - cW / 2;
        y = (height - (ascent + descent)) / 2 + ascent;
        gc.drawString(content, x, y);
        return im;
    }

    /**
     * 根据名字生成头像，英文采用第一个字母，中文2个字使用2个字，超过2个字采用第一个字
     *
     * @param name
     *            名字
     * @return 头像
     */
    public static BufferedImage createAvatar(String name) {
        return createAvatar(name, 0, null, null, null, 0, Font.BOLD);
    }

    /**
     * 根据名字生成头像，英文采用第一个字母，中文2个字使用2个字，超过2个字采用第一个字
     *
     * @param name
     *            名字
     * @param size
     *            图片大小，默认256
     * @param fontColor
     *            文字颜色 默认白色
     * @param bgColor
     *            背景颜色 默认黑色
     * @param fontName
     *            字体名称 需运行环境中已有该字体名称
     * @param fontSize
     *            字体大小
     * @param fontStyle
     *            字体样式 Font.PLAIN || Font.BOLD || Font.ITALIC
     * @return 头像
     */
    public static BufferedImage createAvatar(String name,
                                             int size,
                                             String fontColor,
                                             String bgColor,
                                             String fontName,
                                             int fontSize,
                                             int fontStyle) {
        // 检查输入项目
        if (Strings.isBlank(name)) {
            return null;
        }
        // 分析要写入的文字
        String content = name;
        if (name.length() > 2) {
            content = ("" + name.charAt(0));
        }
        content = content.toUpperCase();
        return createText(content, size, size, fontColor, bgColor, fontName, fontSize, fontStyle);
    }

    /**
     * 根据指定文字内容，生成验证码，字体颜色随机变化。
     *
     * @param content
     *            文字内容
     * @return 图像
     */
    public static BufferedImage createCaptcha(String content) {
        return createCaptcha(content, 0, 0, null, "FFF", null);
    }

    /**
     * 根据指定文字内容，生成验证码
     *
     * @param content
     *            文字内容
     * @param width
     *            图片宽度
     * @param height
     *            图片高度
     * @param fontColor
     *            文字颜色 默认黑色
     * @param bgColor
     *            背景颜色 默认白色
     * @return 图像
     */
    public static BufferedImage createCaptcha(String content,
                                              int width,
                                              int height,
                                              String fontColor,
                                              String bgColor,
                                              String fontName) {
        // 处理下参数
        if (Strings.isBlank(content)) {
            return null;
        }
        boolean isChinese = Strings.isChineseCharacter(content.charAt(0));
        if (width <= 0) {
            // 中文字体的话，间距需要多一些
            width = content.length() * (isChinese ? 25 : 20) + 20;
        }
        if (height <= 0) {
            height = 30;
        }
        Color userColor = Strings.isBlank(fontColor) ? null : Colors.as(fontColor);
        Color colorBg = Strings.isBlank(bgColor) ? Colors.randomColor() : Colors.as(bgColor);

        // 生成背景
        BufferedImage im = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D gc = im.createGraphics();
        gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        gc.setBackground(colorBg);
        gc.clearRect(0, 0, width, height);

        // 加入干扰线
        for (int i = 0; i < 7; i++) {
            gc.setColor(userColor == null ? Colors.randomColor(5, 250) : userColor);
            int x = R.random(0, width);
            int y = R.random(0, height);
            int x1 = R.random(0, width);
            int y1 = R.random(0, height);
            gc.drawLine(x, y, x1, y1);
        }

        // 写入文字
        int rx = 10;
        int ry = isChinese ? height - 8 : height - 10;
        for (int i = 0; i < content.length(); i++) {
            int fontStyle = R.random(0, 3);
            int fontSize = R.random(height - 10, height - 5);
            Font textFont = Strings.isBlank(fontName) ? Fonts.random(fontStyle, fontSize)
                                                      : Fonts.get(fontName, fontStyle, fontSize);
            gc.setColor(userColor == null ? Colors.randomColor(10, 250) : userColor);
            gc.setFont(textFont);
            // 设置字体旋转角度
            int degree = R.random(0, 64) % 30;
            // 正向角度
            gc.rotate(degree * Math.PI / 180, rx, ry);
            gc.drawString(content.charAt(i) + "", rx, ry);
            // 反向角度
            gc.rotate(-degree * Math.PI / 180, rx, ry);
            rx += (isChinese ? 5 : 0) + width / (content.length() + 2);
        }

        // 图像扭曲
        im = twist(im, 1, bgColor);
        return im;
    }

}


/***
 * ASM: a very small and fast Java bytecode manipulation framework
 * Copyright (c) 2000-2011 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.nutz.repo.org.objectweb.asm;

/**
 * A visitor to visit a Java method. The methods of this class must be called in
 * the following order: ( <tt>visitParameter</tt> )* [
 * <tt>visitAnnotationDefault</tt> ] ( <tt>visitAnnotation</tt> |
 * <tt>visitParameterAnnotation</tt> <tt>visitTypeAnnotation</tt> |
 * <tt>visitAttribute</tt> )* [ <tt>visitCode</tt> ( <tt>visitFrame</tt> |
 * <tt>visitXInsn</tt> | <tt>visitLabel</tt> |
 * <tt>visitInsnAnnotation</tt> | <tt>visitTryCatchBlock</tt> |
 * <tt>visitTryCatchAnnotation</tt> | <tt>visitLocalVariable</tt> |
 * <tt>visitLocalVariableAnnotation</tt> | <tt>visitLineNumber</tt> )*
 * <tt>visitMaxs</tt> ] <tt>visitEnd</tt>. In addition, the
 * <tt>visitXInsn</tt> and <tt>visitLabel</tt> methods must be called in
 * the sequential order of the bytecode instructions of the visited code,
 * <tt>visitInsnAnnotation</tt> must be called after the annotated
 * instruction, <tt>visitTryCatchBlock</tt> must be called before the
 * labels passed as arguments have been visited,
 * <tt>visitTryCatchBlockAnnotation</tt> must be called after the
 * corresponding try catch block has been visited, and the
 * <tt>visitLocalVariable</tt>, <tt>visitLocalVariableAnnotation</tt> and
 * <tt>visitLineNumber</tt> methods must be called after the labels
 * passed as arguments have been visited.
 *
 * @author Eric Bruneton
 */
public abstract class MethodVisitor {

    /**
     * The ASM API version implemented by this visitor. The value of this field
     * must be one of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
     */
    protected final int api;

    /**
     * The method visitor to which this visitor must delegate method calls. May
     * be null.
     */
    protected MethodVisitor mv;

    /**
     * Constructs a new {@link MethodVisitor}.
     *
     * @param api
     *            the ASM API version implemented by this visitor. Must be one
     *            of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
     */
    public MethodVisitor(final int api) {
        this(api, null);
    }

    /**
     * Constructs a new {@link MethodVisitor}.
     *
     * @param api
     *            the ASM API version implemented by this visitor. Must be one
     *            of {@link Opcodes#ASM4} or {@link Opcodes#ASM5}.
     * @param mv
     *            the method visitor to which this visitor must delegate method
     *            calls. May be null.
     */
    public MethodVisitor(final int api, final MethodVisitor mv) {
        if (api != Opcodes.ASM4 && api != Opcodes.ASM5) {
            throw new IllegalArgumentException();
        }
        this.api = api;
        this.mv = mv;
    }

    // -------------------------------------------------------------------------
    // Parameters, annotations and non standard attributes
    // -------------------------------------------------------------------------

    /**
     * Visits a parameter of this method.
     *
     * @param name
     *            parameter name or null if none is provided.
     * @param access
     *            the parameter's access flags, only <tt>ACC_FINAL</tt>,
     *            <tt>ACC_SYNTHETIC</tt> or/and <tt>ACC_MANDATED</tt> are
     *            allowed (see {@link Opcodes}).
     */
    public void visitParameter(String name, int access) {
        if (api < Opcodes.ASM5) {
            throw new RuntimeException();
        }
        if (mv != null) {
            mv.visitParameter(name, access);
        }
    }

    /**
     * Visits the default value of this annotation interface method.
     *
     * @return a visitor to the visit the actual default value of this
     *         annotation interface method, or <tt>null</tt> if this visitor is
     *         not interested in visiting this default value. The 'name'
     *         parameters passed to the methods of this annotation visitor are
     *         ignored. Moreover, exacly one visit method must be called on this
     *         annotation visitor, followed by visitEnd.
     */
    public AnnotationVisitor visitAnnotationDefault() {
        if (mv != null) {
            return mv.visitAnnotationDefault();
        }
        return null;
    }

    /**
     * Visits an annotation of this method.
     *
     * @param desc
     *            the class descriptor of the annotation class.
     * @param visible
     *            <tt>true</tt> if the annotation is visible at runtime.
     * @return a visitor to visit the annotation values, or <tt>null</tt> if
     *         this visitor is not interested in visiting this annotation.
     */
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        if (mv != null) {
            return mv.visitAnnotation(desc, visible);
        }
        return null;
    }

    /**
     * Visits an annotation on a type in the method signature.
     *
     * @param typeRef
     *            a reference to the annotated type. The sort of this type
     *            reference must be {@link TypeReference#METHOD_TYPE_PARAMETER
     *            METHOD_TYPE_PARAMETER},
     *            {@link TypeReference#METHOD_TYPE_PARAMETER_BOUND
     *            METHOD_TYPE_PARAMETER_BOUND},
     *            {@link TypeReference#METHOD_RETURN METHOD_RETURN},
     *            {@link TypeReference#METHOD_RECEIVER METHOD_RECEIVER},
     *            {@link TypeReference#METHOD_FORMAL_PARAMETER
     *            METHOD_FORMAL_PARAMETER} or {@link TypeReference#THROWS
     *            THROWS}. See {@link TypeReference}.
     * @param typePath
     *            the path to the annotated type argument, wildcard bound, array
     *            element type, or static inner type within 'typeRef'. May be
     *            <tt>null</tt> if the annotation targets 'typeRef' as a whole.
     * @param desc
     *            the class descriptor of the annotation class.
     * @param visible
     *            <tt>true</tt> if the annotation is visible at runtime.
     * @return a visitor to visit the annotation values, or <tt>null</tt> if
     *         this visitor is not interested in visiting this annotation.
     */
    public AnnotationVisitor visitTypeAnnotation(int typeRef,
            TypePath typePath, String desc, boolean visible) {
        if (api < Opcodes.ASM5) {
            throw new RuntimeException();
        }
        if (mv != null) {
            return mv.visitTypeAnnotation(typeRef, typePath, desc, visible);
        }
        return null;
    }

    /**
     * Visits an annotation of a parameter this method.
     *
     * @param parameter
     *            the parameter index.
     * @param desc
     *            the class descriptor of the annotation class.
     * @param visible
     *            <tt>true</tt> if the annotation is visible at runtime.
     * @return a visitor to visit the annotation values, or <tt>null</tt> if
     *         this visitor is not interested in visiting this annotation.
     */
    public AnnotationVisitor visitParameterAnnotation(int parameter,
            String desc, boolean visible) {
        if (mv != null) {
            return mv.visitParameterAnnotation(parameter, desc, visible);
        }
        return null;
    }

    /**
     * Visits a non standard attribute of this method.
     *
     * @param attr
     *            an attribute.
     */
    public void visitAttribute(Attribute attr) {
        if (mv != null) {
            mv.visitAttribute(attr);
        }
    }

    /**
     * Starts the visit of the method's code, if any (i.e. non abstract method).
     */
    public void visitCode() {
        if (mv != null) {
            mv.visitCode();
        }
    }

    /**
     * Visits the current state of the local variables and operand stack
     * elements. This method must(*) be called just before any
     * instruction i that follows an unconditional branch instruction
     * such as GOTO or THROW, that is the target of a jump instruction, or that
     * starts an exception handler block. The visited types must describe the
     * values of the local variables and of the operand stack elements just
     * before i is executed.

     *

     * (*) this is mandatory only for classes whose version is greater than or
     * equal to {@link Opcodes#V1_6 V1_6}.

     *

     * The frames of a method must be given either in expanded form, or in
     * compressed form (all frames must use the same format, i.e. you must not
     * mix expanded and compressed frames within a single method):
     *

     *
In expanded form, all frames must have the F_NEW type.

     *
In compressed form, frames are basically "deltas" from the state of
     * the previous frame:
     *

     *
{@link Opcodes#F_SAME} representing frame with exactly the same
     * locals as the previous frame and with the empty stack.

     *
{@link Opcodes#F_SAME1} representing frame with exactly the same
     * locals as the previous frame and with single value on the stack (
     * nStack is 1 and stack[0] contains value for the
     * type of the stack item).

     *
{@link Opcodes#F_APPEND} representing frame with current locals are
     * the same as the locals in the previous frame, except that additional
     * locals are defined (nLocal is 1, 2 or 3 and
     * local elements contains values representing added types).

     *
{@link Opcodes#F_CHOP} representing frame with current locals are the
     * same as the locals in the previous frame, except that the last 1-3 locals
     * are absent and with the empty stack (nLocals is 1, 2 or 3).

     *
{@link Opcodes#F_FULL} representing complete frame data.

     *

     *

     *

     *

     * In both cases the first frame, corresponding to the method's parameters
     * and access flags, is implicit and must not be visited. Also, it is
     * illegal to visit two or more frames for the same code location (i.e., at
     * least one instruction must be visited between two calls to visitFrame).
     *
     * @param type
     *            the type of this stack map frame. Must be
     *            {@link Opcodes#F_NEW} for expanded frames, or
     *            {@link Opcodes#F_FULL}, {@link Opcodes#F_APPEND},
     *            {@link Opcodes#F_CHOP}, {@link Opcodes#F_SAME} or
     *            {@link Opcodes#F_APPEND}, {@link Opcodes#F_SAME1} for
     *            compressed frames.
     * @param nLocal
     *            the number of local variables in the visited frame.
     * @param local
     *            the local variable types in this frame. This array must not be
     *            modified. Primitive types are represented by
     *            {@link Opcodes#TOP}, {@link Opcodes#INTEGER},
     *            {@link Opcodes#FLOAT}, {@link Opcodes#LONG},
     *            {@link Opcodes#DOUBLE},{@link Opcodes#NULL} or
     *            {@link Opcodes#UNINITIALIZED_THIS} (long and double are
     *            represented by a single element). Reference types are
     *            represented by String objects (representing internal names),
     *            and uninitialized types by Label objects (this label
     *            designates the NEW instruction that created this uninitialized
     *            value).
     * @param nStack
     *            the number of operand stack elements in the visited frame.
     * @param stack
     *            the operand stack types in this frame. This array must not be
     *            modified. Its content has the same format as the "local"
     *            array.
     * @throws IllegalStateException
     *             if a frame is visited just after another one, without any
     *             instruction between the two (unless this frame is a
     *             Opcodes#F_SAME frame, in which case it is silently ignored).
     */
    public void visitFrame(int type, int nLocal, Object[] local, int nStack,
            Object[] stack) {
        if (mv != null) {
            mv.visitFrame(type, nLocal, local, nStack, stack);
        }
    }

    // -------------------------------------------------------------------------
    // Normal instructions
    // -------------------------------------------------------------------------

    /**
     * Visits a zero operand instruction.
     *
     * @param opcode
     *            the opcode of the instruction to be visited. This opcode is
     *            either NOP, ACONST_NULL, ICONST_M1, ICONST_0, ICONST_1,
     *            ICONST_2, ICONST_3, ICONST_4, ICONST_5, LCONST_0, LCONST_1,
     *            FCONST_0, FCONST_1, FCONST_2, DCONST_0, DCONST_1, IALOAD,
     *            LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD,
     *            IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE,
     *            SASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1,
     *            DUP2_X2, SWAP, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB, DSUB,
     *            IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM,
     *            FREM, DREM, INEG, LNEG, FNEG, DNEG, ISHL, LSHL, ISHR, LSHR,
     *            IUSHR, LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, I2L, I2F, I2D,
     *            L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S,
     *            LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IRETURN, LRETURN, FRETURN,
     *            DRETURN, ARETURN, RETURN, ARRAYLENGTH, ATHROW, MONITORENTER,
     *            or MONITOREXIT.
     */
    public void visitInsn(int opcode) {
        if (mv != null) {
            mv.visitInsn(opcode);
        }
    }

    /**
     * Visits an instruction with a single int operand.
     *
     * @param opcode
     *            the opcode of the instruction to be visited. This opcode is
     *            either BIPUSH, SIPUSH or NEWARRAY.
     * @param operand
     *            the operand of the instruction to be visited.

     *            When opcode is BIPUSH, operand value should be between
     *            Byte.MIN_VALUE and Byte.MAX_VALUE.

     *            When opcode is SIPUSH, operand value should be between
     *            Short.MIN_VALUE and Short.MAX_VALUE.

     *            When opcode is NEWARRAY, operand value should be one of
     *            {@link Opcodes#T_BOOLEAN}, {@link Opcodes#T_CHAR},
     *            {@link Opcodes#T_FLOAT}, {@link Opcodes#T_DOUBLE},
     *            {@link Opcodes#T_BYTE}, {@link Opcodes#T_SHORT},
     *            {@link Opcodes#T_INT} or {@link Opcodes#T_LONG}.
     */
    public void visitIntInsn(int opcode, int operand) {
        if (mv != null) {
            mv.visitIntInsn(opcode, operand);
        }
    }

    /**
     * Visits a local variable instruction. A local variable instruction is an
     * instruction that loads or stores the value of a local variable.
     *
     * @param opcode
     *            the opcode of the local variable instruction to be visited.
     *            This opcode is either ILOAD, LLOAD, FLOAD, DLOAD, ALOAD,
     *            ISTORE, LSTORE, FSTORE, DSTORE, ASTORE or RET.
     * @param var
     *            the operand of the instruction to be visited. This operand is
     *            the index of a local variable.
     */
    public void visitVarInsn(int opcode, int var) {
        if (mv != null) {
            mv.visitVarInsn(opcode, var);
        }
    }

    /**
     * Visits a type instruction. A type instruction is an instruction that
     * takes the internal name of a class as parameter.
     *
     * @param opcode
     *            the opcode of the type instruction to be visited. This opcode
     *            is either NEW, ANEWARRAY, CHECKCAST or INSTANCEOF.
     * @param type
     *            the operand of the instruction to be visited. This operand
     *            must be the internal name of an object or array class (see
     *            {@link Type#getInternalName() getInternalName}).
     */
    public void visitTypeInsn(int opcode, String type) {
        if (mv != null) {
            mv.visitTypeInsn(opcode, type);
        }
    }

    /**
     * Visits a field instruction. A field instruction is an instruction that
     * loads or stores the value of a field of an object.
     *
     * @param opcode
     *            the opcode of the type instruction to be visited. This opcode
     *            is either GETSTATIC, PUTSTATIC, GETFIELD or PUTFIELD.
     * @param owner
     *            the internal name of the field's owner class (see
     *            {@link Type#getInternalName() getInternalName}).
     * @param name
     *            the field's name.
     * @param desc
     *            the field's descriptor (see {@link Type Type}).
     */
    public void visitFieldInsn(int opcode, String owner, String name,
            String desc) {
        if (mv != null) {
            mv.visitFieldInsn(opcode, owner, name, desc);
        }
    }

    /**
     * Visits a method instruction. A method instruction is an instruction that
     * invokes a method.
     *
     * @param opcode
     *            the opcode of the type instruction to be visited. This opcode
     *            is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or
     *            INVOKEINTERFACE.
     * @param owner
     *            the internal name of the method's owner class (see
     *            {@link Type#getInternalName() getInternalName}).
     * @param name
     *            the method's name.
     * @param desc
     *            the method's descriptor (see {@link Type Type}).
     */
    @Deprecated
    public void visitMethodInsn(int opcode, String owner, String name,
            String desc) {
        if (api >= Opcodes.ASM5) {
            boolean itf = opcode == Opcodes.INVOKEINTERFACE;
            visitMethodInsn(opcode, owner, name, desc, itf);
            return;
        }
        if (mv != null) {
            mv.visitMethodInsn(opcode, owner, name, desc);
        }
    }

    /**
     * Visits a method instruction. A method instruction is an instruction that
     * invokes a method.
     *
     * @param opcode
     *            the opcode of the type instruction to be visited. This opcode
     *            is either INVOKEVIRTUAL, INVOKESPECIAL, INVOKESTATIC or
     *            INVOKEINTERFACE.
     * @param owner
     *            the internal name of the method's owner class (see
     *            {@link Type#getInternalName() getInternalName}).
     * @param name
     *            the method's name.
     * @param desc
     *            the method's descriptor (see {@link Type Type}).
     * @param itf
     *            if the method's owner class is an interface.
     */
    public void visitMethodInsn(int opcode, String owner, String name,
            String desc, boolean itf) {
        if (api < Opcodes.ASM5) {
            if (itf != (opcode == Opcodes.INVOKEINTERFACE)) {
                throw new IllegalArgumentException(
                        "INVOKESPECIAL/STATIC on interfaces require ASM 5");
            }
            visitMethodInsn(opcode, owner, name, desc);
            return;
        }
        if (mv != null) {
            mv.visitMethodInsn(opcode, owner, name, desc, itf);
        }
    }

    /**
     * Visits an invokedynamic instruction.
     *
     * @param name
     *            the method's name.
     * @param desc
     *            the method's descriptor (see {@link Type Type}).
     * @param bsm
     *            the bootstrap method.
     * @param bsmArgs
     *            the bootstrap method constant arguments. Each argument must be
     *            an {@link Integer}, {@link Float}, {@link Long},
     *            {@link Double}, {@link String}, {@link Type} or {@link Handle}
     *            value. This method is allowed to modify the content of the
     *            array so a caller should expect that this array may change.
     */
    public void visitInvokeDynamicInsn(String name, String desc, Handle bsm,
            Object... bsmArgs) {
        if (mv != null) {
            mv.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs);
        }
    }

    /**
     * Visits a jump instruction. A jump instruction is an instruction that may
     * jump to another instruction.
     *
     * @param opcode
     *            the opcode of the type instruction to be visited. This opcode
     *            is either IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE, IF_ICMPEQ,
     *            IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE,
     *            IF_ACMPEQ, IF_ACMPNE, GOTO, JSR, IFNULL or IFNONNULL.
     * @param label
     *            the operand of the instruction to be visited. This operand is
     *            a label that designates the instruction to which the jump
     *            instruction may jump.
     */
    public void visitJumpInsn(int opcode, Label label) {
        if (mv != null) {
            mv.visitJumpInsn(opcode, label);
        }
    }

    /**
     * Visits a label. A label designates the instruction that will be visited
     * just after it.
     *
     * @param label
     *            a {@link Label Label} object.
     */
    public void visitLabel(Label label) {
        if (mv != null) {
            mv.visitLabel(label);
        }
    }

    // -------------------------------------------------------------------------
    // Special instructions
    // -------------------------------------------------------------------------

    /**
     * Visits a LDC instruction. Note that new constant types may be added in
     * future versions of the Java Virtual Machine. To easily detect new
     * constant types, implementations of this method should check for
     * unexpected constant types, like this:
     *
     *

     * if (cst instanceof Integer) {
     *     // ...
     * } else if (cst instanceof Float) {
     *     // ...
     * } else if (cst instanceof Long) {
     *     // ...
     * } else if (cst instanceof Double) {
     *     // ...
     * } else if (cst instanceof String) {
     *     // ...
     * } else if (cst instanceof Type) {
     *     int sort = ((Type) cst).getSort();
     *     if (sort == Type.OBJECT) {
     *         // ...
     *     } else if (sort == Type.ARRAY) {
     *         // ...
     *     } else if (sort == Type.METHOD) {
     *         // ...
     *     } else {
     *         // throw an exception
     *     }
     * } else if (cst instanceof Handle) {
     *     // ...
     * } else {
     *     // throw an exception
     * }
     *

     *
     * @param cst
     *            the constant to be loaded on the stack. This parameter must be
     *            a non null {@link Integer}, a {@link Float}, a {@link Long}, a
     *            {@link Double}, a {@link String}, a {@link Type} of OBJECT or
     *            ARRAY sort for <tt>.class</tt> constants, for classes whose
     *            version is 49.0, a {@link Type} of METHOD sort or a
     *            {@link Handle} for MethodType and MethodHandle constants, for
     *            classes whose version is 51.0.
     */
    public void visitLdcInsn(Object cst) {
        if (mv != null) {
            mv.visitLdcInsn(cst);
        }
    }

    /**
     * Visits an IINC instruction.
     *
     * @param var
     *            index of the local variable to be incremented.
     * @param increment
     *            amount to increment the local variable by.
     */
    public void visitIincInsn(int var, int increment) {
        if (mv != null) {
            mv.visitIincInsn(var, increment);
        }
    }

    /**
     * Visits a TABLESWITCH instruction.
     *
     * @param min
     *            the minimum key value.
     * @param max
     *            the maximum key value.
     * @param dflt
     *            beginning of the default handler block.
     * @param labels
     *            beginnings of the handler blocks. <tt>labels[i]</tt> is the
     *            beginning of the handler block for the <tt>min + i</tt> key.
     */
    public void visitTableSwitchInsn(int min, int max, Label dflt,
            Label... labels) {
        if (mv != null) {
            mv.visitTableSwitchInsn(min, max, dflt, labels);
        }
    }

    /**
     * Visits a LOOKUPSWITCH instruction.
     *
     * @param dflt
     *            beginning of the default handler block.
     * @param keys
     *            the values of the keys.
     * @param labels
     *            beginnings of the handler blocks. <tt>labels[i]</tt> is the
     *            beginning of the handler block for the <tt>keys[i]</tt> key.
     */
    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
        if (mv != null) {
            mv.visitLookupSwitchInsn(dflt, keys, labels);
        }
    }

    /**
     * Visits a MULTIANEWARRAY instruction.
     *
     * @param desc
     *            an array type descriptor (see {@link Type Type}).
     * @param dims
     *            number of dimensions of the array to allocate.
     */
    public void visitMultiANewArrayInsn(String desc, int dims) {
        if (mv != null) {
            mv.visitMultiANewArrayInsn(desc, dims);
        }
    }

    /**
     * Visits an annotation on an instruction. This method must be called just
     * after the annotated instruction. It can be called several times
     * for the same instruction.
     *
     * @param typeRef
     *            a reference to the annotated type. The sort of this type
     *            reference must be {@link TypeReference#INSTANCEOF INSTANCEOF},
     *            {@link TypeReference#NEW NEW},
     *            {@link TypeReference#CONSTRUCTOR_REFERENCE
     *            CONSTRUCTOR_REFERENCE}, {@link TypeReference#METHOD_REFERENCE
     *            METHOD_REFERENCE}, {@link TypeReference#CAST CAST},
     *            {@link TypeReference#CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT
     *            CONSTRUCTOR_INVOCATION_TYPE_ARGUMENT},
     *            {@link TypeReference#METHOD_INVOCATION_TYPE_ARGUMENT
     *            METHOD_INVOCATION_TYPE_ARGUMENT},
     *            {@link TypeReference#CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT
     *            CONSTRUCTOR_REFERENCE_TYPE_ARGUMENT}, or
     *            {@link TypeReference#METHOD_REFERENCE_TYPE_ARGUMENT
     *            METHOD_REFERENCE_TYPE_ARGUMENT}. See {@link TypeReference}.
     * @param typePath
     *            the path to the annotated type argument, wildcard bound, array
     *            element type, or static inner type within 'typeRef'. May be
     *            <tt>null</tt> if the annotation targets 'typeRef' as a whole.
     * @param desc
     *            the class descriptor of the annotation class.
     * @param visible
     *            <tt>true</tt> if the annotation is visible at runtime.
     * @return a visitor to visit the annotation values, or <tt>null</tt> if
     *         this visitor is not interested in visiting this annotation.
     */
    public AnnotationVisitor visitInsnAnnotation(int typeRef,
            TypePath typePath, String desc, boolean visible) {
        if (api < Opcodes.ASM5) {
            throw new RuntimeException();
        }
        if (mv != null) {
            return mv.visitInsnAnnotation(typeRef, typePath, desc, visible);
        }
        return null;
    }

    // -------------------------------------------------------------------------
    // Exceptions table entries, debug information, max stack and max locals
    // -------------------------------------------------------------------------

    /**
     * Visits a try catch block.
     *
     * @param start
     *            beginning of the exception handler's scope (inclusive).
     * @param end
     *            end of the exception handler's scope (exclusive).
     * @param handler
     *            beginning of the exception handler's code.
     * @param type
     *            internal name of the type of exceptions handled by the
     *            handler, or <tt>null</tt> to catch any exceptions (for
     *            "finally" blocks).
     * @throws IllegalArgumentException
     *             if one of the labels has already been visited by this visitor
     *             (by the {@link #visitLabel visitLabel} method).
     */
    public void visitTryCatchBlock(Label start, Label end, Label handler,
            String type) {
        if (mv != null) {
            mv.visitTryCatchBlock(start, end, handler, type);
        }
    }

    /**
     * Visits an annotation on an exception handler type. This method must be
     * called after the {@link #visitTryCatchBlock} for the annotated
     * exception handler. It can be called several times for the same exception
     * handler.
     *
     * @param typeRef
     *            a reference to the annotated type. The sort of this type
     *            reference must be {@link TypeReference#EXCEPTION_PARAMETER
     *            EXCEPTION_PARAMETER}. See {@link TypeReference}.
     * @param typePath
     *            the path to the annotated type argument, wildcard bound, array
     *            element type, or static inner type within 'typeRef'. May be
     *            <tt>null</tt> if the annotation targets 'typeRef' as a whole.
     * @param desc
     *            the class descriptor of the annotation class.
     * @param visible
     *            <tt>true</tt> if the annotation is visible at runtime.
     * @return a visitor to visit the annotation values, or <tt>null</tt> if
     *         this visitor is not interested in visiting this annotation.
     */
    public AnnotationVisitor visitTryCatchAnnotation(int typeRef,
            TypePath typePath, String desc, boolean visible) {
        if (api < Opcodes.ASM5) {
            throw new RuntimeException();
        }
        if (mv != null) {
            return mv.visitTryCatchAnnotation(typeRef, typePath, desc, visible);
        }
        return null;
    }

    /**
     * Visits a local variable declaration.
     *
     * @param name
     *            the name of a local variable.
     * @param desc
     *            the type descriptor of this local variable.
     * @param signature
     *            the type signature of this local variable. May be
     *            <tt>null</tt> if the local variable type does not use generic
     *            types.
     * @param start
     *            the first instruction corresponding to the scope of this local
     *            variable (inclusive).
     * @param end
     *            the last instruction corresponding to the scope of this local
     *            variable (exclusive).
     * @param index
     *            the local variable's index.
     * @throws IllegalArgumentException
     *             if one of the labels has not already been visited by this
     *             visitor (by the {@link #visitLabel visitLabel} method).
     */
    public void visitLocalVariable(String name, String desc, String signature,
            Label start, Label end, int index) {
        if (mv != null) {
            mv.visitLocalVariable(name, desc, signature, start, end, index);
        }
    }

    /**
     * Visits an annotation on a local variable type.
     *
     * @param typeRef
     *            a reference to the annotated type. The sort of this type
     *            reference must be {@link TypeReference#LOCAL_VARIABLE
     *            LOCAL_VARIABLE} or {@link TypeReference#RESOURCE_VARIABLE
     *            RESOURCE_VARIABLE}. See {@link TypeReference}.
     * @param typePath
     *            the path to the annotated type argument, wildcard bound, array
     *            element type, or static inner type within 'typeRef'. May be
     *            <tt>null</tt> if the annotation targets 'typeRef' as a whole.
     * @param start
     *            the fist instructions corresponding to the continuous ranges
     *            that make the scope of this local variable (inclusive).
     * @param end
     *            the last instructions corresponding to the continuous ranges
     *            that make the scope of this local variable (exclusive). This
     *            array must have the same size as the 'start' array.
     * @param index
     *            the local variable's index in each range. This array must have
     *            the same size as the 'start' array.
     * @param desc
     *            the class descriptor of the annotation class.
     * @param visible
     *            <tt>true</tt> if the annotation is visible at runtime.
     * @return a visitor to visit the annotation values, or <tt>null</tt> if
     *         this visitor is not interested in visiting this annotation.
     */
    public AnnotationVisitor visitLocalVariableAnnotation(int typeRef,
            TypePath typePath, Label[] start, Label[] end, int[] index,
            String desc, boolean visible) {
        if (api < Opcodes.ASM5) {
            throw new RuntimeException();
        }
        if (mv != null) {
            return mv.visitLocalVariableAnnotation(typeRef, typePath, start,
                    end, index, desc, visible);
        }
        return null;
    }

    /**
     * Visits a line number declaration.
     *
     * @param line
     *            a line number. This number refers to the source file from
     *            which the class was compiled.
     * @param start
     *            the first instruction corresponding to this line number.
     * @throws IllegalArgumentException
     *             if <tt>start</tt> has not already been visited by this
     *             visitor (by the {@link #visitLabel visitLabel} method).
     */
    public void visitLineNumber(int line, Label start) {
        if (mv != null) {
            mv.visitLineNumber(line, start);
        }
    }

    /**
     * Visits the maximum stack size and the maximum number of local variables
     * of the method.
     *
     * @param maxStack
     *            maximum stack size of the method.
     * @param maxLocals
     *            maximum number of local variables for the method.
     */
    public void visitMaxs(int maxStack, int maxLocals) {
        if (mv != null) {
            mv.visitMaxs(maxStack, maxLocals);
        }
    }

    /**
     * Visits the end of the method. This method, which is the last one to be
     * called, is used to inform the visitor that all the annotations and
     * attributes of the method have been visited.
     */
    public void visitEnd() {
        if (mv != null) {
            mv.visitEnd();
        }
    }
}


package org.nutz.mvc;

import java.text.MessageFormat;
import java.util.Map;

/**
 * web环境下 国际化 相关帮助函数
 *
 * @author 306955302@qq.com
 */
public class MvcI18n {

    /**
     * 取得国际化信息
     *
     * @param key
     * @return
     */
    public static String message(String key) {
        return messageOrDefault(key, "");
    }

    /**
     * 取得国际化信息并格式化
     * {0}帐号登录{1}  ->>  test帐号登录失败
     *
     * @param key
     * @return
     */
    public static String message(String key, Object... params) {
        return MessageFormat.format(messageOrDefault(key, ""), params);
    }

    /**
     * 取得国际化信息并格式化
     * {0}帐号登录{1}  ->>  test帐号登录失败
     *
     * @param key
     * @return
     */
    public static String messageOrDefaultFormat(String key, String defaultValue, Object... params) {
        return MessageFormat.format(messageOrDefault(key, defaultValue), params);
    }

    /**
     * 取得国际化信息
     *
     * @param key
     * @return
     */
    public static String messageOrDefault(String key, String defaultValue) {
        String localizationKey = Mvcs.getLocalizationKey() == null ? Mvcs.getDefaultLocalizationKey() : Mvcs.getLocalizationKey();
        Map<String, Object> localization = Mvcs.getLocaleMessage(localizationKey);
        if (null != localization) {
            Object value = localization.get(key);
            return value == null ? defaultValue : String.valueOf(value);
        }
        return defaultValue;
    }

}


package org.nutz.dao.util.cri;

import static org.nutz.dao.util.cri.Exps.*;

import java.util.ArrayList;
import java.util.List;

import org.nutz.dao.Cnd;
import org.nutz.dao.entity.Entity;
import org.nutz.dao.impl.sql.pojo.AbstractPItem;
import org.nutz.dao.jdbc.ValueAdaptor;
import org.nutz.dao.sql.Pojo;
import org.nutz.log.Log;
import org.nutz.log.Logs;

/**
 * 组合一组表达式，只能增加，不能减少
 */
public class SqlExpressionGroup extends AbstractPItem implements SqlExpression {

    private static final long serialVersionUID = 1L;

    private List<SqlExpression> exps;

    protected boolean not;

    private static final Log log = Logs.get();

    public SqlExpressionGroup() {
        exps = new ArrayList<SqlExpression>(); // 默认就是10个，能放5个条件，够了吧
    }

    public SqlExpressionGroup and(String name, String op, Object value) {
        return and(Exps.create(name, op, value));
    }

    public SqlExpressionGroup and(SqlExpression exp) {
    	if (exp == null) {
    		if (log.isTraceEnabled())
    			log.trace("ignore null SqlExpression");
    		return this;
    	}
        if (!exps.isEmpty())
            _add(new Static("AND"));
        return _add(exp);
    }

    public SqlExpressionGroup andEquals(String name, Object val) {
        if (null == val)
            return andIsNull(name);
        return and(eq(name, val));
    }

    public SqlExpressionGroup andNotEquals(String name, Object val) {
        if (null == val)
            return andNotIsNull(name);
        return and(eq(name, val).not());

    }

    public SqlExpressionGroup andIsNull(String name) {
        return and(isNull(name));
    }

    public SqlExpressionGroup andNotIsNull(String name) {
        return and(isNull(name).not());
    }

    public SqlExpressionGroup andGT(String name, long val) {
        return and(gt(name, val));
    }

    public SqlExpressionGroup andGTE(String name, long val) {
        return and(gte(name, val));
    }

    public SqlExpressionGroup andLT(String name, long val) {
        return and(lt(name, val));
    }

    public SqlExpressionGroup andLTE(String name, long val) {
        return and(lte(name, val));
    }

    public SqlExpressionGroup andIn(String name, long... ids) {
        return and(inLong(name, ids));
    }

    public SqlExpressionGroup andInArray(String name, long[] ids) {
    	return and(inLong(name, ids));
    }

    public SqlExpressionGroup andInList(String name, List<Long> ids) {
    	return and(inLong(name, ids.toArray(new Long[ids.size()])));
    }

    public SqlExpressionGroup andInIntArray(String name, int... ids) {
        return and(inInt(name, ids));
    }

    public SqlExpressionGroup andInIntArray2(String name, int[] ids) {
    	return and(inInt(name, ids));
    }

    public SqlExpressionGroup andInIntList(String name, List<Integer> ids) {
    	return and(inInt(name, ids.toArray(new Integer[ids.size()])));
    }

    public SqlExpressionGroup andIn(String name, String... names) {
        return and(inStr(name, names));
    }

    public SqlExpressionGroup andInStrArray(String name, String[] names) {
    	return and(inStr(name, names));
    }

    public SqlExpressionGroup andInStrList(String name, List<String> names) {
    	return and(inStr(name, names.toArray(new String[names.size()])));
    }

    /**
     * 用法
     * cnd.where().andInBySql("dept_id","SELECT id FROM sys_dept  WHERE FIND_IN_SET ('%s',ancestors)", deptId)；
     * @param name 查询条件
     * @param subSql sql
     * @param args 参数
     * @return
     */
    public SqlExpressionGroup andInBySql(String name, String subSql, Object... args) {
        return and(inSql(name, subSql, args));
    }

    public SqlExpressionGroup andNotInBySql(String name, String subSql, Object... args) {
        return and(inSql(name, subSql, args).not());
    }

    public SqlExpressionGroup andInBySql2(String name, String subSql, Object... values) {
        return and(inSql2(name, subSql, values));
    }

    public SqlExpressionGroup andNotInBySql2(String name, String subSql, Object... values) {
        return and(inSql2(name, subSql, values).not());
    }

    public SqlExpressionGroup andNotIn(String name, long... ids) {
        return and(inLong(name, ids).not());
    }

    public SqlExpressionGroup andNotInArray(String name, long[] ids) {
    	return and(inLong(name, ids).not());
    }

    public SqlExpressionGroup andNotInList(String name, List<Long> ids) {
    	return and(inLong(name, ids.toArray(new Long[ids.size()])).not());
    }

    public SqlExpressionGroup andNotIn(String name, int... ids) {
        return and(inInt(name, ids).not());
    }

    public SqlExpressionGroup andNotInArray(String name, int[] ids) {
    	return and(inInt(name, ids).not());
    }

    public SqlExpressionGroup andNotInIntList(String name, List<Integer> ids) {
    	return and(inInt(name, ids.toArray(new Integer[ids.size()])).not());
    }

    public SqlExpressionGroup andNotIn(String name, String... names) {
        return and(inStr(name, names).not());
    }

    public SqlExpressionGroup andNotInArray(String name, String[] names) {
    	return and(inStr(name, names).not());
    }

    public SqlExpressionGroup andNotInStrList(String name, List<String> names) {
    	return and(inStr(name, names.toArray(new String[names.size()])).not());
    }

    public SqlExpressionGroup andLike(String name, String value) {
        return and(like(name, value));
    }

    public SqlExpressionGroup andLikeL(String name, String value) {
        return and(like(name, value).left(null));
    }

    public SqlExpressionGroup andLikeR(String name, String value) {
        return and(like(name, value).right(null));
    }

    public SqlExpressionGroup andNotLike(String name, String value) {
        return and(like(name, value).not());
    }

    public SqlExpressionGroup andNotLikeL(String name, String value) {
        return and(like(name, value).left(null).not());
    }

    public SqlExpressionGroup andNotLikeR(String name, String value) {
        return and(like(name, value).right(null).not());
    }

    public SqlExpressionGroup andLike(String name, String value, boolean ignoreCase) {
        return and(like(name, value, ignoreCase));
    }

    public SqlExpressionGroup andNotLike(String name, String value, boolean ignoreCase) {
        return and(like(name, value, ignoreCase).not());
    }

    public SqlExpressionGroup andLike(String name, String value, String left, String right, boolean ignoreCase) {
        return and(like(name, value, ignoreCase).left(left).right(right));
    }

    public SqlExpressionGroup andNotLike(String name, String value, String left, String right, boolean ignoreCase) {
        return and(like(name, value, ignoreCase).left(left).right(right).not());
    }

    public SqlExpressionGroup or(String name, String op, Object value) {
        return or(Exps.create(name, op, value));
    }

    public SqlExpressionGroup or(SqlExpression exp) {
        if (exp == null) {
            if (log.isTraceEnabled())
                log.trace("ignore null SqlExpression");
            return this;
        }
        if (!exps.isEmpty())
            _add(new Static("OR"));
        return _add(exp);
    }

    public SqlExpressionGroup orEquals(String name, Object val) {
        return or(eq(name, val));
    }

    public SqlExpressionGroup orNotEquals(String name, Object val) {
        return or(eq(name, val).not());

    }

    public SqlExpressionGroup orIsNull(String name) {
        return or(isNull(name));
    }

    public SqlExpressionGroup orNotIsNull(String name) {
        return or(isNull(name).not());
    }

    public SqlExpressionGroup orGT(String name, long val) {
        return or(gt(name, val));
    }

    public SqlExpressionGroup orGTE(String name, long val) {
        return or(gte(name, val));
    }

    public SqlExpressionGroup orLT(String name, long val) {
        return or(lt(name, val));
    }

    public SqlExpressionGroup orLTE(String name, long val) {
        return or(lte(name, val));
    }

    public SqlExpressionGroup orIn(String name, long... ids) {
        return or(inLong(name, ids));
    }

    public SqlExpressionGroup orIn(String name, int... ids) {
        return or(inInt(name, ids));
    }

    public SqlExpressionGroup orIn(String name, String... names) {
        return or(inStr(name, names));
    }

    public SqlExpressionGroup orInBySql(String name, String subSql, Object... args) {
        return or(inSql(name, subSql, args));
    }

    public SqlExpressionGroup orNotInBySql(String name, String subSql, Object... args) {
        return or(inSql(name, subSql, args).not());
    }

    public SqlExpressionGroup orInBySql2(String name, String subSql, Object... values) {
        return or(inSql2(name, subSql, values));
    }

    public SqlExpressionGroup orNotInBySql2(String name, String subSql, Object... values) {
        return or(inSql2(name, subSql, values).not());
    }

    public SqlExpressionGroup orNotIn(String name, long... ids) {
        return or(inLong(name, ids).not());
    }

    public SqlExpressionGroup orNotIn(String name, int... ids) {
        return or(inInt(name, ids).not());
    }

    public SqlExpressionGroup orNotIn(String name, String... names) {
        return or(inStr(name, names).not());
    }

    public SqlExpressionGroup orLike(String name, String value) {
        return or(like(name, value));
    }

    public SqlExpressionGroup orLikeL(String name, String value) {
        return or(like(name, value).left(null));
    }

    public SqlExpressionGroup orLikeR(String name, String value) {
        return or(like(name, value).right(null));
    }

    public SqlExpressionGroup orNotLike(String name, String value) {
        return or(like(name, value).not());
    }

    public SqlExpressionGroup orNotLikeL(String name, String value) {
        return or(like(name, value).left(null).not());
    }

    public SqlExpressionGroup orNotLikeR(String name, String value) {
        return or(like(name, value).right(null).not());
    }

    public SqlExpressionGroup orLike(String name, String value, boolean ignoreCase) {
        return or(like(name, value, ignoreCase));
    }

    public SqlExpressionGroup orNotLike(String name, String value, boolean ignoreCase) {
        return or(like(name, value, ignoreCase).not());
    }

    public SqlExpressionGroup orLike(String name, String value, String left, String right, boolean ignoreCase) {
        return or(like(name, value, ignoreCase).left(left).right(right));
    }

    public SqlExpressionGroup orNotLike(String name, String value, String left, String right, boolean ignoreCase) {
        return or(like(name, value, ignoreCase).left(left).right(right).not());
    }

    //------------------ between
    public SqlExpressionGroup andBetween(String name, Object min, Object max) {
    	return and(new BetweenExpression(name, min, max));
    }

    public SqlExpressionGroup orBetween(String name, Object min, Object max) {
    	return or(new BetweenExpression(name, min, max));
    }

    @Override
    public void setPojo(Pojo pojo) {
        super.setPojo(pojo);
        for (SqlExpression exp : exps)
            exp.setPojo(pojo);
    }

    private SqlExpressionGroup _add(SqlExpression exp) {
        if (null != exp) {
            exps.add(exp);
            exp.setPojo(pojo);
            if (exp instanceof SqlExpressionGroup)
                ((SqlExpressionGroup) exp).top = false;
        }
        return this;
    }

    public void joinSql(Entity<?> en, StringBuilder sb) {
        if (!exps.isEmpty()) {
            if (top) {
                sb.append(" WHERE ");
                if (not)
                    sb.append("NOT (");
                for (SqlExpression exp : exps)
                    exp.joinSql(en, sb);
                if (not)
                    sb.append(')');
            } else {
                if (not)
                    sb.append("NOT ");
                sb.append('(');
                for (SqlExpression exp : exps)
                    exp.joinSql(en, sb);
                sb.append(')');
            }
        }
    }

    public int joinAdaptor(Entity<?> en, ValueAdaptor[] adaptors, int off) {
        for (SqlExpression exp : exps)
            off = exp.joinAdaptor(en, adaptors, off);
        return off;
    }

    public int joinParams(Entity<?> en, Object obj, Object[] params, int off) {
        for (SqlExpression exp : exps)
            off = exp.joinParams(en, obj, params, off);
        return off;
    }

    public int paramCount(Entity<?> en) {
        int re = 0;
        for (SqlExpression exp : exps)
            re += exp.paramCount(en);
        return re;
    }

    public SqlExpression setNot(boolean not) {
        this.not = not;
        return this;
    }

    public boolean isEmpty() {
        return exps.isEmpty();
    }

    public List<SqlExpression> cloneExps() {
        return new ArrayList<SqlExpression>(exps);
    }

    public List<SqlExpression> getExps() {
		return exps;
	}

    public SqlExpressionGroup clone(){
        SqlExpressionGroup seg = new SqlExpressionGroup();
        seg.exps = cloneExps();
        seg.pojo = this.pojo;
        seg.top = this.top;
        return seg;
    }

    /**
     * 若value为null/空白字符串/空集合/空数组,则本条件不添加.
     * @see Cnd#and(String, String, Object)
     */
    public SqlExpressionGroup andEX(String name, String op, Object value) {
        return and(Cnd.expEX(name, op, value));
    }

    /**
     * 若value为null/空白字符串/空集合/空数组,则本条件不添加.
     * @see Cnd#or(String, String, Object)
     */
    public SqlExpressionGroup orEX(String name, String op, Object value) {
        return or(Cnd.expEX(name, op, value));
    }
}


package org.nutz.aop;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;

import org.nutz.log.Log;
import org.nutz.log.Logs;

/**
 * 拦截器链,持有被调用方法的信息
 *
 * @author wendal(wendal1985@gmail.com)
 *
 */
public class InterceptorChain {

    protected Method callingMethod;

    protected int methodIndex;

    protected Object[] args;

    protected AopCallback callingObj;

    protected Object returnValue;

    protected List<MethodInterceptor> miList;

    private static Log log = Logs.get();

    private boolean invoked = false;

    private int currentMI = 0;

    public InterceptorChain(int methodIndex,
                            Object obj,
                            Method method,
                            List<MethodInterceptor> miList,
                            Object[] args) {
        this.methodIndex = methodIndex;
        this.callingObj = (AopCallback) obj;
        this.callingMethod = method;
        this.args = args;
        this.miList = miList;
    }

    /**
     * 继续执行下一个拦截器,如果已经没有剩下的拦截器,则执行原方法
     *
     * @return 拦截器链本身
     * @throws Throwable
     *             下层拦截器或原方法抛出的一切异常
     */
    public InterceptorChain doChain() throws Throwable {
        if (currentMI == miList.size())
            invoke();
        else {
            currentMI++;
            miList.get(currentMI - 1).filter(this);
        }
        return this;

    }

    /**
     * 执行原方法,一般情况下不应该直接被调用
     *
     * @throws Throwable
     *             原方法抛出的一切异常
     */
    public void invoke() throws Throwable {
        if (invoked)
            log.warnf("!! Calling Method more than once! Method --> %s", callingMethod.toString());
        else
            invoked = true;
        this.returnValue = callingObj._aop_invoke(methodIndex, args);
    }

    /**
     * 获取返回值
     *
     * @return 返回值
     */
    public Object getReturn() {
        return returnValue;
    }

    /**
     * 正在被调用的Method
     */
    public Method getCallingMethod() {
        return callingMethod;
    }

    /**
     * 方法调用的参数数组,如果你要改变参数,那么必须保证参数类型与方法参数兼容.
     */
    public Object[] getArgs() {
        return args;
    }

    public void setReturnValue(Object returnValue) {
        this.returnValue = returnValue;
    }

    public AopCallback getCallingObj() {
        return callingObj;
    }

    public boolean isInvoked() {
        return invoked;
    }

    /**
     * 获取当前的方法拦截器列表,注意,这个列表是不可修改的.如果需要修改,那么请通过{@link #setMethodInterceptors(List)}
     */
    public List<MethodInterceptor> getMethodInterceptors() {
        return Collections.unmodifiableList(miList);
    }

    /**
     * 设置当前调用的方法拦截器列表,注意,这个set只对当前方法调用有效.
     */
    public void setMethodInterceptors(List<MethodInterceptor> miList) {
        this.miList = miList;
    }
}