{
        if (homeArticleData != null && mLatestProjectAdapter != null) {
            mLatestProjectAdapter.setData(position, homeArticleData);
        }
        ToastUtils.showToast(msg);
    }

package mao.com.mao_wanandroid_client.view.main.fragment;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import mao.com.mao_wanandroid_client.R;
import mao.com.mao_wanandroid_client.application.Constants;
import mao.com.mao_wanandroid_client.base.fragment.RootBaseFragment;
import mao.com.mao_wanandroid_client.model.modelbean.home.HomeArticleData;
import mao.com.mao_wanandroid_client.presenter.main.HomePageSecondTabContract;
import mao.com.mao_wanandroid_client.presenter.main.HomeSecondTabPresenter;
import mao.com.mao_wanandroid_client.utils.StartDetailPage;
import mao.com.mao_wanandroid_client.utils.ToastUtils;
import mao.com.mao_wanandroid_client.view.main.adapter.HomeLatestProjectAdapter;

/**
 * @author maoqitian
 * @Description 首页第二个 tab Fragment (最新项目)
 * @Time 2019/5/21 0021 21:06
 */
public class HomeSecondTabFragment extends RootBaseFragment<HomeSecondTabPresenter> implements HomePageSecondTabContract.HomePageSecondTabView, BaseQuickAdapter.OnItemClickListener {

    @BindView(R.id.inflate_view)
    SmartRefreshLayout mSmartRefreshLayout;

    @BindView(R.id.home_page_recyclerview)
    RecyclerView mRecyclerView;

    private RecyclerView.LayoutManager layoutManager;

    private HomeLatestProjectAdapter mLatestProjectAdapter;

    private List<HomeArticleData> mHomeArticleDataList;

    private String mTabTitle;

    // 该id在获取该分类下项目时需要用到
    private int projectId;

    /**
     * @param tabName tab name
     * @param id 该id在获取该分类下项目时需要用到
     * @return
     */
    public static Fragment newInstance(String tabName, int id) {
        Bundle args = new Bundle();
        args.putString(Constants.TAG_TAB_NAME, tabName);
        args.putInt(Constants.BUNDLE_PROJECT_ID, id);
        HomeSecondTabFragment fragment = new HomeSecondTabFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    protected void initView() {
        super.initView();
        initSecondTabRecyclerView();
    }

    private void initSecondTabRecyclerView() {
        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);
        // use a linear layout manager
        layoutManager = new LinearLayoutManager(_mActivity);
        mRecyclerView.setLayoutManager(layoutManager);
        // specify an adapter
        mHomeArticleDataList = new ArrayList<>();
        initLatestProjectPage();
    }

    // 最新项目页面 init
    private void initLatestProjectPage() {
        mLatestProjectAdapter = new HomeLatestProjectAdapter(R.layout.article_project_item_cardview_layout);
        mLatestProjectAdapter.setOnItemClickListener(this);
        mRecyclerView.setAdapter(mLatestProjectAdapter);
        setSmartRefreshLayoutListener();
        setHomePageItemClickListener();
    }

    private void setHomePageItemClickListener() {
        mLatestProjectAdapter.setOnItemChildClickListener((adapter, view, position) -> {
            HomeArticleData homeArticleData = (HomeArticleData) adapter.getItem(position);
            switch(view.getId()) {
                case // 点击收藏
                R.id.image_project_collect:
                    Log.e("毛麒添", "点击收藏");
                    if (homeArticleData != null) {
                        addOrCancelCollect(position, homeArticleData);
                    }
                    break;
                case R.id.tv_project_tag:
                    // 点击项目tag
                    StartDetailPage.start(_mActivity, homeArticleData, Constants.RESULT_CODE_HOME_PAGE, Constants.ACTION_KNOWLEDGE_LEVEL2_ACTIVITY);
                    break;
            }
        });
    }

    // 收藏或者取消收藏
    private void setSmartRefreshLayoutListener() {
        mSmartRefreshLayout.setOnRefreshListener(new OnRefreshListener() {

            @Override
            public void onRefresh(@NonNull RefreshLayout refreshLayout) {
                Log.e("毛麒添", "下拉加载");
                mPresenter.getRefreshPage(projectId);
                refreshLayout.autoRefresh();
            }
        });
        // 加载更多
        mSmartRefreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {

            @Override
            public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
                Log.e("毛麒添", "加载更多");
                mPresenter.getLoadMorePage(projectId);
                refreshLayout.autoLoadMore();
            }
        });
    }

    @Override
    protected int getLayoutId() {
        return R.layout.home_first_tab_fragment_layout;
    }

    @Override
    protected void initEventAndData() {
        super.initEventAndData();
        showLoading();
        if (getArguments() != null) {
            mTabTitle = getArguments().getString(Constants.TAG_TAB_NAME);
            projectId = getArguments().getInt(Constants.BUNDLE_PROJECT_ID);
            Log.e("毛麒添", "首页mTabTitle " + mTabTitle);
        }
        mPresenter.getProjectListDate(false, projectId);
        if (-1 != projectId) {
            // 项目模块不显示tag
            mLatestProjectAdapter.isShowTag(false);
        }
    }

    @Override
    public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
        HomeArticleData homeArticleData = (HomeArticleData) adapter.getItem(position);
        StartDetailPage.start(_mActivity, homeArticleData, Constants.PAGE_WEB_COLLECT, Constants.ACTION_PAGE_DETAIL_ACTIVITY);
    }

    @Override
    public void showHomeLatestProjectList(boolean isRefreshData, List<HomeArticleData> homeArticleDataList) {
        Log.e("毛麒添", "首页 最新项目 数据 " + homeArticleDataList.toString());
        if (isRefreshData) {
            // 加载更多
            mLatestProjectAdapter.addData(homeArticleDataList);
        } else {
            mHomeArticleDataList.clear();
            mHomeArticleDataList.addAll(homeArticleDataList);
            mLatestProjectAdapter.replaceData(mHomeArticleDataList);
            showNormal();
        }
        mSmartRefreshLayout.finishRefresh();
        mSmartRefreshLayout.finishLoadMore();
    }

    @Override
    public void showAddArticleCollectStatus(int position, HomeArticleData homeArticleData, String msg) {
        showCollectStatus(position, homeArticleData, msg);
    }

    @Override
    public void showCancelArticleCollectStatus(int position, HomeArticleData homeArticleData, String msg) {
        showCollectStatus(position, homeArticleData, msg);
    }

    // 显示收藏 或取消 收藏之后的状态
    private void showCollectStatus(int position, HomeArticleData homeArticleData, String msg)

    @Override
    public void reload() {
        showLoading();
        mPresenter.getProjectListDate(false, projectId);
    }
}


package mao.com.mao_wanandroid_client.view.main.fragment;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import mao.com.mao_wanandroid_client.R;
import mao.com.mao_wanandroid_client.application.Constants;
import mao.com.mao_wanandroid_client.base.fragment.RootBaseFragment;
import mao.com.mao_wanandroid_client.model.modelbean.home.HomeArticleData;
import mao.com.mao_wanandroid_client.presenter.main.HomePageSecondTabContract;
import mao.com.mao_wanandroid_client.presenter.main.HomeSecondTabPresenter;
import mao.com.mao_wanandroid_client.utils.StartDetailPage;
import mao.com.mao_wanandroid_client.utils.ToastUtils;
import mao.com.mao_wanandroid_client.view.main.adapter.HomeLatestProjectAdapter;

/**
 * @author maoqitian
 * @Description 首页第二个 tab Fragment (最新项目)
 * @Time 2019/5/21 0021 21:06
 */
public class HomeSecondTabFragment extends RootBaseFragment<HomeSecondTabPresenter> implements HomePageSecondTabContract.HomePageSecondTabView, BaseQuickAdapter.OnItemClickListener {

    @BindView(R.id.inflate_view)
    SmartRefreshLayout mSmartRefreshLayout;

    @BindView(R.id.home_page_recyclerview)
    RecyclerView mRecyclerView;

    private RecyclerView.LayoutManager layoutManager;

    private HomeLatestProjectAdapter mLatestProjectAdapter;

    private List<HomeArticleData> mHomeArticleDataList;

    private String mTabTitle;

    // 该id在获取该分类下项目时需要用到
    private int projectId;

    /**
     * @param tabName tab name
     * @param id 该id在获取该分类下项目时需要用到
     * @return
     */
    public static Fragment newInstance(String tabName, int id) {
        Bundle args = new Bundle();
        args.putString(Constants.TAG_TAB_NAME, tabName);
        args.putInt(Constants.BUNDLE_PROJECT_ID, id);
        HomeSecondTabFragment fragment = new HomeSecondTabFragment();
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    protected void initView() {
        super.initView();
        initSecondTabRecyclerView();
    }

    private void initSecondTabRecyclerView() {
        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);
        // use a linear layout manager
        layoutManager = new LinearLayoutManager(_mActivity);
        mRecyclerView.setLayoutManager(layoutManager);
        // specify an adapter
        mHomeArticleDataList = new ArrayList<>();
        initLatestProjectPage();
    }

    // 最新项目页面 init
    private void initLatestProjectPage() {
        mLatestProjectAdapter = new HomeLatestProjectAdapter(R.layout.article_project_item_cardview_layout);
        mLatestProjectAdapter.setOnItemClickListener(this);
        mRecyclerView.setAdapter(mLatestProjectAdapter);
        setSmartRefreshLayoutListener();
        setHomePageItemClickListener();
    }

    private void setHomePageItemClickListener() {
        mLatestProjectAdapter.setOnItemChildClickListener((adapter, view, position) -> {
            HomeArticleData homeArticleData = (HomeArticleData) adapter.getItem(position);
            switch(view.getId()) {
                case // 点击收藏
                R.id.image_project_collect:
                    Log.e("毛麒添", "点击收藏");
                    if (homeArticleData != null) {
                        addOrCancelCollect(position, homeArticleData);
                    }
                    break;
                case R.id.tv_project_tag:
                    // 点击项目tag
                    StartDetailPage.start(_mActivity, homeArticleData, Constants.RESULT_CODE_HOME_PAGE, Constants.ACTION_KNOWLEDGE_LEVEL2_ACTIVITY);
                    break;
            }
        });
    }

    // 收藏或者取消收藏
    private void setSmartRefreshLayoutListener() {
        mSmartRefreshLayout.setOnRefreshListener(new OnRefreshListener() {

            @Override
            public void onRefresh(@NonNull RefreshLayout refreshLayout) {
                Log.e("毛麒添", "下拉加载");
                mPresenter.getRefreshPage(projectId);
                refreshLayout.autoRefresh();
            }
        });
        // 加载更多
        mSmartRefreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {

            @Override
            public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
                Log.e("毛麒添", "加载更多");
                mPresenter.getLoadMorePage(projectId);
                refreshLayout.autoLoadMore();
            }
        });
    }

    @Override
    protected int getLayoutId() {
        return R.layout.home_first_tab_fragment_layout;
    }

    @Override
    protected void initEventAndData() {
        super.initEventAndData();
        showLoading();
        if (getArguments() != null) {
            mTabTitle = getArguments().getString(Constants.TAG_TAB_NAME);
            projectId = getArguments().getInt(Constants.BUNDLE_PROJECT_ID);
            Log.e("毛麒添", "首页mTabTitle " + mTabTitle);
        }
        mPresenter.getProjectListDate(false, projectId);
        if (-1 != projectId) {
            // 项目模块不显示tag
            mLatestProjectAdapter.isShowTag(false);
        }
    }

    @Override
    public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
        HomeArticleData homeArticleData = (HomeArticleData) adapter

        StartDetailPage.start(_mActivity, homeArticleData, Constants.PAGE_WEB_COLLECT, Constants.ACTION_PAGE_DETAIL_ACTIVITY);
    }

    @Override
    public void showHomeLatestProjectList(boolean isRefreshData, List<HomeArticleData> homeArticleDataList) {
        Log.e("毛麒添", "首页 最新项目 数据 " + homeArticleDataList.toString());
        if (isRefreshData) {
            // 加载更多
            mLatestProjectAdapter.addData(homeArticleDataList);
        } else {
            mHomeArticleDataList.clear();
            mHomeArticleDataList.addAll(homeArticleDataList);
            mLatestProjectAdapter.replaceData(mHomeArticleDataList);
            showNormal();
        }
        mSmartRefreshLayout.finishRefresh();
        mSmartRefreshLayout.finishLoadMore();
    }

    @Override
    public void showAddArticleCollectStatus(int position, HomeArticleData homeArticleData, String msg) {
        showCollectStatus(position, homeArticleData, msg);
    }

    @Override
    public void showCancelArticleCollectStatus(int position, HomeArticleData homeArticleData, String msg) {
        showCollectStatus(position, homeArticleData, msg);
    }

    // 显示收藏 或取消 收藏之后的状态
    private void showCollectStatus(int position, HomeArticleData homeArticleData, String msg) {
        if (homeArticleData != null && mLatestProjectAdapter != null) {
            mLatestProjectAdapter.setData(position, homeArticleData);
        }
        ToastUtils.showToast(msg);
    }

    @Override
    public void reload() {
        showLoading();
        mPresenter.getProjectListDate(false, projectId);
    }
}


public static Fragment newInstance(String tabName, int id) {
        Bundle args = new Bundle();
        args.putString(Constants.TAG_TAB_NAME, tabName);
        args.putInt(Constants.BUNDLE_PROJECT_ID, id);
        HomeSecondTabFragment fragment = new HomeSecondTabFragment();
        fragment.setArguments(args);
        return fragment;
    }

package mao.com.mao_wanandroid_client.view.main.fragment;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import mao.com.mao_wanandroid_client.R;
import mao.com.mao_wanandroid_client.application.Constants;
import mao.com.mao_wanandroid_client.base.fragment.RootBaseFragment;
import mao.com.mao_wanandroid_client.model.modelbean.home.HomeArticleData;
import mao.com.mao_wanandroid_client.presenter.main.HomePageSecondTabContract;
import mao.com.mao_wanandroid_client.presenter.main.HomeSecondTabPresenter;
import mao.com.mao_wanandroid_client.utils.StartDetailPage;
import mao.com.mao_wanandroid_client.utils.ToastUtils;
import mao.com.mao_wanandroid_client.view.main.adapter.HomeLatestProjectAdapter;

/**
 * @author maoqitian
 * @Description 首页第二个 tab Fragment (最新项目)
 * @Time 2019/5/21 0021 21:06
 */
public class HomeSecondTabFragment extends RootBaseFragment<HomeSecondTabPresenter> implements HomePageSecondTabContract.HomePageSecondTabView, BaseQuickAdapter.OnItemClickListener {

    @BindView(R.id.inflate_view)
    SmartRefreshLayout mSmartRefreshLayout;

    @BindView(R.id.home_page_recyclerview)
    RecyclerView mRecyclerView;

    private RecyclerView.LayoutManager layoutManager;

    private HomeLatestProjectAdapter mLatestProjectAdapter;

    private List<HomeArticleData> mHomeArticleDataList;

    private String mTabTitle;

    // 该id在获取该分类下项目时需要用到
    private int projectId;

    /**
     * @param tabName tab name
     * @param id 该id在获取该分类下项目时需要用到
     * @return
     */

    @Override
    protected void initView() {
        super.initView();
        initSecondTabRecyclerView();
    }

    private void initSecondTabRecyclerView() {
        // use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        mRecyclerView.setHasFixedSize(true);
        // use a linear layout manager
        layoutManager = new LinearLayoutManager(_mActivity);
        mRecyclerView.setLayoutManager(layoutManager);
        // specify an adapter
        mHomeArticleDataList = new ArrayList<>();
        initLatestProjectPage();
    }

    // 最新项目页面 init
    private void initLatestProjectPage() {
        mLatestProjectAdapter = new HomeLatestProjectAdapter(R.layout.article_project_item_cardview_layout);
        mLatestProjectAdapter.setOnItemClickListener(this);
        mRecyclerView.setAdapter(mLatestProjectAdapter);
        setSmartRefreshLayoutListener();
        setHomePageItemClickListener();
    }

    private void setHomePageItemClickListener() {
        mLatestProjectAdapter.setOnItemChildClickListener((adapter, view, position) -> {
            HomeArticleData homeArticleData = (HomeArticleData) adapter.getItem(position);
            switch(view.getId()) {
                case // 点击收藏
                R.id.image_project_collect:
                    Log.e("毛麒添", "点击收藏");
                    if (homeArticleData != null) {
                        addOrCancelCollect(position, homeArticleData);
                    }
                    break;
                case R.id.tv_project_tag:
                    // 点击项目tag
                    StartDetailPage.start(_mActivity, homeArticleData, Constants.RESULT_CODE_HOME_PAGE, Constants.ACTION_KNOWLEDGE_LEVEL2_ACTIVITY);
                    break;
            }
        });
    }

    // 收藏或者取消收藏
    private void setSmartRefreshLayoutListener() {
        mSmartRefreshLayout.setOnRefreshListener(new OnRefreshListener() {

            @Override
            public void onRefresh(@NonNull RefreshLayout refreshLayout) {
                Log.e("毛麒添", "下拉加载");
                mPresenter.getRefreshPage(projectId);
                refreshLayout.autoRefresh();
            }
        });
        // 加载更多
        mSmartRefreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {

            @Override
            public void onLoadMore(@NonNull RefreshLayout refreshLayout) {
                Log.e("毛麒添", "加载更多");
                mPresenter.getLoadMorePage(projectId);
                refreshLayout.autoLoadMore();
            }
        });
    }

    @Override
    protected int getLayoutId() {
        return R.layout.home_first_tab_fragment_layout;
    }

    @Override
    protected void initEventAndData() {
        super.initEventAndData();
        showLoading();
        if (getArguments() != null) {
            mTabTitle = getArguments().getString(Constants.TAG_TAB_NAME);
            projectId = getArguments().getInt(Constants.BUNDLE_PROJECT_ID);
            Log.e("毛麒添", "首页mTabTitle " + mTabTitle);
        }
        mPresenter.getProjectListDate(false, projectId);
        if (-1 != projectId) {
            // 项目模块不显示tag
            mLatestProjectAdapter.isShowTag(false);
        }
    }

    @Override
    public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
        HomeArticleData homeArticleData = (HomeArticleData) adapter.getItem(position);
        StartDetailPage.start(_mActivity, homeArticleData, Constants.PAGE_WEB_COLLECT, Constants.ACTION_PAGE_DETAIL_ACTIVITY);
    }

    @Override
    public void showHomeLatestProjectList(boolean isRefreshData, List<HomeArticleData> homeArticleDataList) {
        Log.e("毛麒添", "首页 最新项目 数据 " + homeArticleDataList.toString());
        if (isRefreshData) {
            // 加载更多
            mLatestProjectAdapter.addData(homeArticleDataList);
        } else {
            mHomeArticleDataList.clear();
            mHomeArticleDataList.addAll(homeArticleDataList);
            mLatestProjectAdapter.replaceData(mHomeArticleDataList);
            showNormal();
        }
        mSmartRefreshLayout.finishRefresh();
        mSmartRefreshLayout.finishLoadMore();
    }

    @Override
    public void showAddArticleCollectStatus(int position, HomeArticleData homeArticleData, String msg) {
        showCollectStatus(position, homeArticleData, msg);
    }

    @Override
    public void showCancelArticleCollectStatus(int position, HomeArticleData homeArticleData, String msg) {
        showCollectStatus(position, homeArticleData, msg);
    }

    // 显示收藏 或取消 收藏之后的状态
    private void showCollectStatus(int position, HomeArticleData homeArticleData, String msg) {
        if (homeArticleData != null && mLatestProjectAdapter != null) {
            mLatestProjectAdapter.setData(position, homeArticleData);
        }
        ToastUtils.showToast(msg);
    }

    @Override
    public void reload() {
        showLoading();
        mPresenter.getProjectListDate(false, projectId);
    }
}


{
        try {
            return (Object[]) METHODS[mid].invoke(this, new CCArguments(args));
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            if (e.getCause() instanceof IllegalArgumentException)
                throw new LuaException(e.getCause().getMessage());
            else
                throw new RuntimeException(e.getCause());
        }
    }

package net.montoyo.wd.entity;

import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraftforge.fml.common.Optional;
import net.montoyo.wd.core.CCArguments;
import net.montoyo.wd.core.IComputerArgs;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

@Optional.Interface(iface = "dan200.computercraft.api.peripheral.IPeripheral", modid = "computercraft")
public class TileEntityCCInterface extends TileEntityInterfaceBase implements IPeripheral {

    private static final String[] METHOD_NAMES;

    private static final Method[] METHODS;

    static {
        ArrayList<String> names = new ArrayList<>();
        ArrayList<Method> methods = new ArrayList<>();
        Method[] src = TileEntityInterfaceBase.class.getMethods();
        for (Method m : src) {
            if (m.getAnnotation(TileEntityInterfaceBase.ComputerFunc.class) != null) {
                if (m.getParameterCount() != 1 || m.getParameterTypes()[0] != IComputerArgs.class)
                    throw new RuntimeException("Found @ComputerFunc method with invalid arguments");
                if (m.getReturnType() != Object[].class)
                    throw new RuntimeException("Found @ComputerFunc method with invalid return type");
                names.add(m.getName());
                methods.add(m);
            }
        }
        METHOD_NAMES = names.toArray(new String[0]);
        METHODS = methods.toArray(new Method[0]);
    }

    @Nonnull
    @Override
    public String getType() {
        return "webdisplays";
    }

    @Nonnull
    @Override
    public String[] getMethodNames() {
        return METHOD_NAMES;
    }

    @Optional.Method(modid = "computercraft")
    @Nullable
    @Override
    public Object[] callMethod(@Nonnull IComputerAccess ca, @Nonnull ILuaContext ctx, int mid, @Nonnull Object[] args) throws LuaException, InterruptedException

    @Optional.Method(modid = "computercraft")
    @Override
    public boolean equals(@Nullable IPeripheral periph) {
        return periph == this;
    }
}


package net.montoyo.wd.entity;

import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraftforge.fml.common.Optional;
import net.montoyo.wd.core.CCArguments;
import net.montoyo.wd.core.IComputerArgs;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

@Optional.Interface(iface = "dan200.computercraft.api.peripheral.IPeripheral", modid = "computercraft")
public class TileEntityCCInterface extends TileEntityInterfaceBase implements IPeripheral {

    private static final String[] METHOD_NAMES;

    private static final Method[] METHODS;

    static {
        ArrayList<String> names = new ArrayList<>();
        ArrayList<Method> methods = new ArrayList<>();
        Method[] src = TileEntityInterfaceBase.class.getMethods();
        for (Method m : src) {
            if (m.getAnnotation(TileEntityInterfaceBase.ComputerFunc.class) != null) {
                if (m.getParameterCount() != 1 || m.getParameterTypes()[0] != IComputerArgs.class)
                    throw new RuntimeException("Found @ComputerFunc method with invalid arguments");
                if (m.getReturnType() != Object[].class)
                    throw new RuntimeException("Found @ComputerFunc method with invalid return type");
                names.add(m.getName());
                methods.add(m);
            }
        }
        METHOD_NAMES = names.toArray(new String[0]);
        METHODS = methods.toArray(new Method[0]);
    }

    @Nonnull
    @Override
    public String getType() {
        return "webdisplays";
    }

    @Nonnull
    @Override
    public String[] getMethodNames() {
        return ME

    }

    @Optional.Method(modid = "computercraft")
    @Nullable
    @Override
    public Object[] callMethod(@Nonnull IComputerAccess ca, @Nonnull ILuaContext ctx, int mid, @Nonnull Object[] args) throws LuaException, InterruptedException {
        try {
            return (Object[]) METHODS[mid].invoke(this, new CCArguments(args));
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            if (e.getCause() instanceof IllegalArgumentException)
                throw new LuaException(e.getCause().getMessage());
            else
                throw new RuntimeException(e.getCause());
        }
    }

    @Optional.Method(modid = "computercraft")
    @Override
    public boolean equals(@Nullable IPeripheral periph) {
        return periph == this;
    }
}


try {
            return (Object[]) METHODS[mid].invoke(this, new CCArguments(args));
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            if (e.getCause() instanceof IllegalArgumentException)
                throw new LuaException(e.getCause().getMessage());
            else
                throw new RuntimeException(e.getCause());
        }

package net.montoyo.wd.entity;

import dan200.computercraft.api.lua.ILuaContext;
import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral;
import net.minecraftforge.fml.common.Optional;
import net.montoyo.wd.core.CCArguments;
import net.montoyo.wd.core.IComputerArgs;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

@Optional.Interface(iface = "dan200.computercraft.api.peripheral.IPeripheral", modid = "computercraft")
public class TileEntityCCInterface extends TileEntityInterfaceBase implements IPeripheral {

    private static final String[] METHOD_NAMES;

    private static final Method[] METHODS;

    static {
        ArrayList<String> names = new ArrayList<>();
        ArrayList<Method> methods = new ArrayList<>();
        Method[] src = TileEntityInterfaceBase.class.getMethods();
        for (Method m : src) {
            if (m.getAnnotation(TileEntityInterfaceBase.ComputerFunc.class) != null) {
                if (m.getParameterCount() != 1 || m.getParameterTypes()[0] != IComputerArgs.class)
                    throw new RuntimeException("Found @ComputerFunc method with invalid arguments");
                if (m.getReturnType() != Object[].class)
                    throw new RuntimeException("Found @ComputerFunc method with invalid return type");
                names.add(m.getName());
                methods.add(m);
            }
        }
        METHOD_NAMES = names.toArray(new String[0]);
        METHODS = methods.toArray(new Method[0]);
    }

    @Nonnull
    @Override
    public String getType() {
        return "webdisplays";
    }

    @Nonnull
    @Override
    public String[] getMethodNames() {
        return METHOD_NAMES;
    }

    @Optional.Method(modid = "computercraft")
    @Nullable
    @Override
    public Object[] callMethod(@Nonnull IComputerAccess ca, @Nonnull ILuaContext ctx, int mid, @Nonnull Object[] args) throws LuaException, InterruptedException {

    }

    @Optional.Method(modid = "computercraft")
    @Override
    public boolean equals(@Nullable IPeripheral periph) {
        return periph == this;
    }
}


{
        List<AnnotationData<?>> results = null;
        try {
            results = _dataIO.readData(_layer, _dataSerializer, dataIndices);
        } catch (Exception e) {
            LOGGER.error("Error trying to read annotations from HBase", e);
        }
        return results;
    }

package com.oculusinfo.annotation.manager.impl;

import com.oculusinfo.annotation.AnnotationData;
import com.oculusinfo.annotation.io.AnnotationIO;
import com.oculusinfo.annotation.io.impl.HBaseAnnotationIO;
import com.oculusinfo.annotation.io.serialization.AnnotationSerializer;
import com.oculusinfo.annotation.io.serialization.JSONAnnotationDataSerializer;
import com.oculusinfo.annotation.manager.AnnotationManager;
import com.oculusinfo.factory.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;

/**
 * 	This class implements the AnnotationManager interface for use with an HBase data store
 */
public class HBaseAnnotationManager implements AnnotationManager {

    private static final Logger LOGGER = LoggerFactory.getLogger(HBaseAnnotationManager.class);

    private String _layer = null;

    private AnnotationIO _dataIO = null;

    private AnnotationSerializer _dataSerializer = null;

    /**
     * Constructor - initializes internal members as needed to write annotations
     *               into HBase
     *
     * @param layer
     *            layer/table to be used for annotation operations within the HBase data store
     *
     * @param zookeeperQuorum
     *            An HBase configuration parameter, this should match the similar value in hbase-site.xml
     *
     * @param zookeeperPort
     *            An HBase configuration parameter, this should match the similar value in hbase-site.xml
     *
     * @param hbaseMaster
     *            An HBase configuration parameter, this should match the similar value in hbase-site.xml
     */
    public HBaseAnnotationManager(String layer, String zookeeperQuorum, String zookeeperPort, String hbaseMaster) {
        try {
            _dataIO = new HBaseAnnotationIO(zookeeperQuorum, zookeeperPort, hbaseMaster);
        } catch (Exception e) {
            LOGGER.error("Error creating internal HBase IO member", e);
        }
        _dataSerializer = new JSONAnnotationDataSerializer();
        _layer = layer;
    }

    /**
     * Initialize the HBase layer/table for writing.
     */
    @Override
    public void initializeForWrite() throws IOException {
        try {
            _dataIO.initializeForWrite(_layer);
        } catch (Exception e) {
            LOGGER.error("Error trying to initialize HBase table", e);
        }
    }

    /**
     * Write the collection of annotations to HBase
     *
     * @param annotations
     *            This is a list of AnnotationData objects to write into HBase
     */
    @Override
    public void writeAnnotations(List<AnnotationData<?>> annotations) throws IOException {
        try {
            if ((_dataIO != null) && (_layer != null) && (_dataSerializer != null)) {
                _dataIO.writeData(_layer, _dataSerializer, annotations);
            }
        } catch (Exception e) {
            LOGGER.error("Error trying to write annotations to HBase", e);
        }
    }

    /**
     * Remove the collection of annotations from HBase
     *
     * @param dataIndices
     *            This is a list of Pairs that contain the timestamp(String) and UUID(Long) that correspond to
     *            the annotations to be removed from HBase
     */
    @Override
    public void removeAnnotations(Iterable<Pair<String, Long>> dataIndices) throws IOException {
        try {
            _dataIO.removeData(_layer, dataIndices);
        } catch (Exception e) {
            LOGGER.error("Error trying to remove annotations from HBase", e);
        }
    }

    /**
     * Read a collection of annotations from HBase based on the indices passed in
     *
     * @param dataIndices
     *            This is a list of Pairs that contain the timestamp(String) and UUID(Long) that correspond to
     *            the annotations to be read in
     *
     * @return a list of AnnotationData objects retrieved from HBase based on the indices passed in
     */
    @Override
    public List<AnnotationData<?>> readAnnotations(Iterable<Pair<String, Long>> dataIndices) throws IOException


package com.oculusinfo.annotation.manager.impl;

import com.oculusinfo.annotation.AnnotationData;
import com.oculusinfo.annotation.io.AnnotationIO;
import com.oculusinfo.annotation.io.impl.HBaseAnnotationIO;
import com.oculusinfo.annotation.io.serialization.AnnotationSerializer;
import com.oculusinfo.annotation.io.serialization.JSONAnnotationDataSerializer;
import com.oculusinfo.annotation.manager.AnnotationManager;
import com.oculusinfo.factory.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;

/**
 * 	This class implements the AnnotationManager interface for use with an HBase data store
 */
public class HBaseAnnotationManager implements AnnotationManager {

    private static final Logger LOGGER = LoggerFactory.getLogger(HBaseAnnotationManager.class);

    private String _layer = null;

    private AnnotationIO _dataIO = null;

    private AnnotationSerializer _dataSerializer = null;

    /**
     * Constructor - initializes internal members as needed to write annotations
     *               into HBase
     *
     * @param layer
     *            layer/table to be used for annotation operations within the HBase data store
     *
     * @param zookeeperQuorum
     *            An HBase configuration parameter, this should match the similar value in hbase-site.xml
     *
     * @param zookeeperPort
     *            An HBase configuration parameter, this should match the similar value in hbase-site.xml
     *
     * @param hbaseMaster
     *            An HBase configuration parameter, this should match the similar value in hbase-site.xml
     */
    public HBaseAnnotationManager(String layer, String zookeeperQuorum, String zookeeperPort, String hbaseMaster) {
        try {
            _dataIO = new HBaseAnnotationIO(zookeeperQuorum, zookeeperPort, hbaseMaster);
        } catch (Exception e) {
            LOGGER.error("Error creating internal HBase IO member", e);
        }
        _dataSerializer = new JSONAnnotationDataSerializer();
        _layer = layer;
    }

    /**
     * Initialize the HBase layer/table for writing.
     */
    @Override
    public void initializeForWrite() throws IOException {
        try {
            _dataIO.initializeForWrite(_layer);
        } catch (Exception e) {
            LOGGER.error("Error trying to initialize HBase table", e);
        }
    }

    /**
     * Write the collection of annotations to HBase
     *
     * @param annotations
     *            This is a list of AnnotationData objects to write into HBase
     */
    @Override
    public void writeAnnotations(List<AnnotationData<?>> annotations) throws IOException {
        try {
            if ((_dataIO != null) && (_layer != null) && (_dataSerializer != null)) {
                _dataIO.writeData(_layer, _dataSerializer, annotations);
            }
        } catch (Exception e) {
            LOGGER.error("Error trying to write annotations to HBase", e);
        }
    }

    /**
     * Remove the collection of annotations from HBase
     *
     * @param dataIndices
     *            This is a list of Pairs that contain the timestamp(String) and UUID(Long) that correspond to
     *            the annotations to be removed from HBase
     */
    @Override
    public void removeAnnotations(Iterable<Pair<String, Long>> dataIndices) throws IOException {
        try {
            _dataIO.removeData(_layer, dataIndices);
        } catch (Exception e) {
            LOGGER.error("Error trying to remove annotations from HBase", e);
        }
    }

    /**
     * Read a collection of annotations from HBase based on the indices passed in
     *
     * @param dataIndices
     *            This is a list of Pairs that contain the timestamp(String) and UUID(Long) that correspond to
     *            the annotations to be read in
     *
     * @return a list of AnnotationData objects retrieved from HBase based on the indices passed in
     */
    @Override
    public List<AnnotationData<?>> readAnnotations(Iterable<Pair<String, Long>> dataIndices) throws IOException {
        List<Ann

        try {
            results = _dataIO.readData(_layer, _dataSerializer, dataIndices);
        } catch (Exception e) {
            LOGGER.error("Error trying to read annotations from HBase", e);
        }
        return results;
    }
}


@Override
    public void writeAnnotations(List<AnnotationData<?>> annotations) throws IOException {
        try {
            if ((_dataIO != null) && (_layer != null) && (_dataSerializer != null)) {
                _dataIO.writeData(_layer, _dataSerializer, annotations);
            }
        } catch (Exception e) {
            LOGGER.error("Error trying to write annotations to HBase", e);
        }
    }

package com.oculusinfo.annotation.manager.impl;

import com.oculusinfo.annotation.AnnotationData;
import com.oculusinfo.annotation.io.AnnotationIO;
import com.oculusinfo.annotation.io.impl.HBaseAnnotationIO;
import com.oculusinfo.annotation.io.serialization.AnnotationSerializer;
import com.oculusinfo.annotation.io.serialization.JSONAnnotationDataSerializer;
import com.oculusinfo.annotation.manager.AnnotationManager;
import com.oculusinfo.factory.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;

/**
 * 	This class implements the AnnotationManager interface for use with an HBase data store
 */
public class HBaseAnnotationManager implements AnnotationManager {

    private static final Logger LOGGER = LoggerFactory.getLogger(HBaseAnnotationManager.class);

    private String _layer = null;

    private AnnotationIO _dataIO = null;

    private AnnotationSerializer _dataSerializer = null;

    /**
     * Constructor - initializes internal members as needed to write annotations
     *               into HBase
     *
     * @param layer
     *            layer/table to be used for annotation operations within the HBase data store
     *
     * @param zookeeperQuorum
     *            An HBase configuration parameter, this should match the similar value in hbase-site.xml
     *
     * @param zookeeperPort
     *            An HBase configuration parameter, this should match the similar value in hbase-site.xml
     *
     * @param hbaseMaster
     *            An HBase configuration parameter, this should match the similar value in hbase-site.xml
     */
    public HBaseAnnotationManager(String layer, String zookeeperQuorum, String zookeeperPort, String hbaseMaster) {
        try {
            _dataIO = new HBaseAnnotationIO(zookeeperQuorum, zookeeperPort, hbaseMaster);
        } catch (Exception e) {
            LOGGER.error("Error creating internal HBase IO member", e);
        }
        _dataSerializer = new JSONAnnotationDataSerializer();
        _layer = layer;
    }

    /**
     * Initialize the HBase layer/table for writing.
     */
    @Override
    public void initializeForWrite() throws IOException {
        try {
            _dataIO.initializeForWrite(_layer);
        } catch (Exception e) {
            LOGGER.error("Error trying to initialize HBase table", e);
        }
    }

    /**
     * Write the collection of annotations to HBase
     *
     * @param annotations
     *            This is a list of AnnotationData objects to write into HBase
     */

    /**
     * Remove the collection of annotations from HBase
     *
     * @param dataIndices
     *            This is a list of Pairs that contain the timestamp(String) and UUID(Long) that correspond to
     *            the annotations to be removed from HBase
     */
    @Override
    public void removeAnnotations(Iterable<Pair<String, Long>> dataIndices) throws IOException {
        try {
            _dataIO.removeData(_layer, dataIndices);
        } catch (Exception e) {
            LOGGER.error("Error trying to remove annotations from HBase", e);
        }
    }

    /**
     * Read a collection of annotations from HBase based on the indices passed in
     *
     * @param dataIndices
     *            This is a list of Pairs that contain the timestamp(String) and UUID(Long) that correspond to
     *            the annotations to be read in
     *
     * @return a list of AnnotationData objects retrieved from HBase based on the indices passed in
     */
    @Override
    public List<AnnotationData<?>> readAnnotations(Iterable<Pair<String, Long>> dataIndices) throws IOException {
        List<AnnotationData<?>> results = null;
        try {
            results = _dataIO.readData(_layer, _dataSerializer, dataIndices);
        } catch (Exception e) {
            LOGGER.error("Error trying to read annotations from HBase", e);
        }
        return results;
    }
}


try {
            if ((_dataIO != null) && (_layer != null) && (_dataSerializer != null)) {
                _dataIO.writeData(_layer, _dataSerializer, annotations);
            }
        } catch (Exception e) {
            LOGGER.error("Error trying to write annotations to HBase", e);
        }

package com.oculusinfo.annotation.manager.impl;

import com.oculusinfo.annotation.AnnotationData;
import com.oculusinfo.annotation.io.AnnotationIO;
import com.oculusinfo.annotation.io.impl.HBaseAnnotationIO;
import com.oculusinfo.annotation.io.serialization.AnnotationSerializer;
import com.oculusinfo.annotation.io.serialization.JSONAnnotationDataSerializer;
import com.oculusinfo.annotation.manager.AnnotationManager;
import com.oculusinfo.factory.util.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.List;

/**
 * 	This class implements the AnnotationManager interface for use with an HBase data store
 */
public class HBaseAnnotationManager implements AnnotationManager {

    private static final Logger LOGGER = LoggerFactory.getLogger(HBaseAnnotationManager.class);

    private String _layer = null;

    private AnnotationIO _dataIO = null;

    private AnnotationSerializer _dataSerializer = null;

    /**
     * Constructor - initializes internal members as needed to write annotations
     *               into HBase
     *
     * @param layer
     *            layer/table to be used for annotation operations within the HBase data store
     *
     * @param zookeeperQuorum
     *            An HBase configuration parameter, this should match the similar value in hbase-site.xml
     *
     * @param zookeeperPort
     *            An HBase configuration parameter, this should match the similar value in hbase-site.xml
     *
     * @param hbaseMaster
     *            An HBase configuration parameter, this should match the similar value in hbase-site.xml
     */
    public HBaseAnnotationManager(String layer, String zookeeperQuorum, String zookeeperPort, String hbaseMaster) {
        try {
            _dataIO = new HBaseAnnotationIO(zookeeperQuorum, zookeeperPort, hbaseMaster);
        } catch (Exception e) {
            LOGGER.error("Error creating internal HBase IO member", e);
        }
        _dataSerializer = new JSONAnnotationDataSerializer();
        _layer = layer;
    }

    /**
     * Initialize the HBase layer/table for writing.
     */
    @Override
    public void initializeForWrite() throws IOException {
        try {
            _dataIO.initializeForWrite(_layer);
        } catch (Exception e) {
            LOGGER.error("Error trying to initialize HBase table", e);
        }
    }

    /**
     * Write the collection of annotations to HBase
     *
     * @param annotations
     *            This is a list of AnnotationData objects to write into HBase
     */
    @Override
    public void writeAnnotations(List<AnnotationData<?>> annotations) throws IOException {

    }

    /**
     * Remove the collection of annotations from HBase
     *
     * @param dataIndices
     *            This is a list of Pairs that contain the timestamp(String) and UUID(Long) that correspond to
     *            the annotations to be removed from HBase
     */
    @Override
    public void removeAnnotations(Iterable<Pair<String, Long>> dataIndices) throws IOException {
        try {
            _dataIO.removeData(_layer, dataIndices);
        } catch (Exception e) {
            LOGGER.error("Error trying to remove annotations from HBase", e);
        }
    }

    /**
     * Read a collection of annotations from HBase based on the indices passed in
     *
     * @param dataIndices
     *            This is a list of Pairs that contain the timestamp(String) and UUID(Long) that correspond to
     *            the annotations to be read in
     *
     * @return a list of AnnotationData objects retrieved from HBase based on the indices passed in
     */
    @Override
    public List<AnnotationData<?>> readAnnotations(Iterable<Pair<String, Long>> dataIndices) throws IOException {
        List<AnnotationData<?>> results = null;
        try {
            results = _dataIO.readData(_layer, _dataSerializer, dataIndices);
        } catch (Exception e) {
            LOGGER.error("Error trying to read annotations from HBase", e);
        }
        return results;
    }
}


{
        return SnapApp.getDefault().getInstanceName() + " - " + Bundle.CTL_ExportMaskPixelsAction_DialogTitle();
    }

package org.esa.snap.rcp.actions.file.export;

import com.bc.ceres.core.ProgressMonitor;
import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker;
import org.esa.snap.core.datamodel.*;
import org.esa.snap.core.util.SystemUtils;
import org.esa.snap.core.util.io.FileUtils;
import org.esa.snap.core.util.io.SnapFileFilter;
import org.esa.snap.core.util.math.MathUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.rcp.util.MultiSizeIssue;
import org.esa.snap.ui.AbstractDialog;
import org.esa.snap.ui.ModalDialog;
import org.esa.snap.ui.SelectExportMethodDialog;
import org.esa.snap.ui.UIUtils;
import org.esa.snap.ui.product.ProductSceneView;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.util.ContextAwareAction;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;

@ActionID(category = "File", id = "org.esa.snap.rcp.actions.file.export.ExportMaskPixelsAction")
@ActionRegistration(displayName = "#CTL_ExportMaskPixelsAction_MenuText", popupText = "#CTL_ExportMaskPixelsAction_PopupText", lazy = false)
@ActionReferences({ @ActionReference(path = "Menu/File/Export/Other", position = 50), @ActionReference(path = "Menu/Raster/Export", position = 100), @ActionReference(path = "Context/ProductSceneView", position = 50) })
@NbBundle.Messages({ "CTL_ExportMaskPixelsAction_MenuText=Mask Pixels", "CTL_ExportMaskPixelsAction_PopupText=Export Mask Pixels", "CTL_ExportMaskPixelsAction_DialogTitle=Export Mask Pixels", "CTL_ExportMaskPixelsAction_ShortDescription=Export Mask Pixels." })
public class ExportMaskPixelsAction extends AbstractAction implements ContextAwareAction, LookupListener, HelpCtx.Provider {

    private static final String HELP_ID = "exportMaskPixels";

    private static final String ERR_MSG_BASE = "Mask pixels cannot be exported:\n";

    private final Lookup.Result<ProductSceneView> result;

    public ExportMaskPixelsAction() {
        this(Utilities.actionsGlobalContext());
    }

    public ExportMaskPixelsAction(Lookup lkp) {
        super(Bundle.CTL_ExportMaskPixelsAction_MenuText());
        putValue("popupText", Bundle.CTL_ExportMaskPixelsAction_PopupText());
        result = lkp.lookupResult(ProductSceneView.class);
        result.addLookupListener(WeakListeners.create(LookupListener.class, this, result));
        setEnabled(false);
    }

    /**
     * Invoked when a command action is performed.
     *
     * @param event the command event
     */
    @Override
    public void actionPerformed(ActionEvent event) {
        ProductSceneView sceneView = SnapApp.getDefault().getSelectedProductSceneView();
        if (sceneView != null) {
            Product product = sceneView.getProduct();
            exportMaskPixels(product);
        }
    }

    @Override
    public Action createContextAwareInstance(Lookup lkp) {
        return new ExportMaskPixelsAction(lkp);
    }

    @Override
    public void resultChanged(LookupEvent le) {
        ProductSceneView sceneView = SnapApp.getDefault().getSelectedProductSceneView();
        boolean enabled = false;
        if (sceneView != null) {
            Product product = sceneView.getProduct();
            enabled = product.getMaskGroup().getNodeCount() > 0;
        }
        setEnabled(enabled);
    }

    // Private implementations for the "export Mask Pixels" command
    /**
     * Performs the actual "export Mask Pixels" command.
     * @param product
     */
    private void exportMaskPixels(Product product) {
        String[] maskNames = product.getMaskGroup().getNodeNames();
        final String maskName;
        if (maskNames.length == 1) {
            maskName = maskNames[0];
        } else {
            JPanel panel = new JPanel();
            BoxLayout boxLayout = new BoxLayout(panel, BoxLayout.X_AXIS);
            panel.setLayout(boxLayout);
            panel.add(new JLabel("Select Mask: "));
            JComboBox<String> maskCombo = new JComboBox<>(maskNames);
            panel.add(maskCombo);
            ModalDialog modalDialog = new ModalDialog(SnapApp.getDefault().getMainFrame(), Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), panel, ModalDialog.ID_OK_CANCEL | ModalDialog.ID_HELP, getHelpCtx().getHelpID());
            if (modalDialog.show() == AbstractDialog.ID_OK) {
                maskName = (String) maskCombo.getSelectedItem();
            } else {
                return;
            }
        }
        final RenderedImage maskImage = product.getMaskGroup().get(maskName).getSourceImage();
        if (maskImage == null) {
            Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + "No Mask image available.");
            return;
        }
        // Compute total number of Mask pixels
        final long numMaskPixels = getNumMaskPixels(maskImage, product.getSceneRasterWidth(), product.getSceneRasterHeight());
        String numPixelsText;
        if (numMaskPixels == 1) {
            numPixelsText = "One Mask pixel will be exported.\n";
        } else {
            numPixelsText = numMaskPixels + " Mask pixels will be exported.\n";
        }
        // Get export method from user
        final String questionText = "How do you want to export the pixel values?\n";
        final JCheckBox createHeaderBox = new JCheckBox("Create header");
        final JCheckBox exportTiePointsBox = new JCheckBox("Export tie-points");
        final JCheckBox exportWavelengthsAndSFBox = new JCheckBox("Export wavelengths + solar fluxes");
        final int method = SelectExportMethodDialog.run(SnapApp.getDefault().getMainFrame(), getWindowTitle(), questionText + numPixelsText, new JCheckBox[] { createHeaderBox, exportTiePointsBox, exportWavelengthsAndSFBox }, getHelpCtx().getHelpID());
        final boolean mustCreateHeader = createHeaderBox.isSelected();
        final boolean mustExportTiePoints = exportTiePointsBox.isSelected();
        final boolean mustExportWavelengthsAndSF = exportWavelengthsAndSFBox.isSelected();
        final PrintWriter out;
        final StringBuffer clipboardText;
        final int initialBufferSize = 256000;
        if (method == SelectExportMethodDialog.EXPORT_TO_CLIPBOARD) {
            // Write into string buffer
            final StringWriter stringWriter = new StringWriter(initialBufferSize);
            out = new PrintWriter(stringWriter);
            clipboardText = stringWriter.getBuffer();
        } else if (method == SelectExportMethodDialog.EXPORT_TO_FILE) {
            // Write into file, get file from user
            final File file = promptForFile(createDefaultFileName(product, maskName));
            if (file == null) {
                // Cancel
                return;
            }
            final FileWriter fileWriter;
            try {
                fileWriter = new FileWriter(file);
            } catch (IOException e) {
                Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + "Failed to create file '" + file + "':\n" + e.getMessage());
                // Error
                return;
            }
            out = new PrintWriter(new BufferedWriter(fileWriter, initialBufferSize));
            clipboardText = null;
        } else {
            // Cancel
            return;
        }
        final ProgressMonitorSwingWorker<Exception, Object> swingWorker = new ProgressMonitorSwingWorker<Exception, Object>(SnapApp.getDefault().getMainFrame(), Bundle.CTL_ExportMaskPixelsAction_DialogTitle()) {

            @Override
            protected Exception doInBackground(ProgressMonitor pm) throws Exception {
                Exception returnValue = null;
                try {
                    boolean success = exportMaskPixels(out, product, maskName, mustCreateHeader, mustExportTiePoints, mustExportWavelengthsAndSF, pm);
                    if (success && clipboardText != null) {
                        SystemUtils.copyToClipboard(clipboardText.toString());
                        clipboardText.setLength(0);
                    }
                } catch (Exception e) {
                    returnValue = e;
                } finally {
                    out.close();
                }
                return returnValue;
            }

            @Override
            public void done() {
                // clear status bar
                SnapApp.getDefault().setStatusBarMessage("");
                UIUtils.setRootFrameDefaultCursor(SnapApp.getDefault().getMainFrame());
                // On error, show error message
                Exception exception;
                try {
                    exception = get();
                } catch (Exception e) {
                    exception = e;
                }
                if (exception != null) {
                    Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + exception.getMessage());
                }
            }
        };
        // show wait-cursor
        UIUtils.setRootFrameWaitCursor(SnapApp.getDefault().getMainFrame());
        // show message in status bar
        SnapApp.getDefault().setStatusBarMessage("Exporting Mask pixels...");
        // Start separate worker thread.
        swingWorker.execute();
    }

    private static String createDefaultFileName(final Product raster, String maskName) {
        String productName = FileUtils.getFilenameWithoutExtension(raster.getProduct().getName());
        return productName + "_" + maskName + "_Mask.txt";
    }

    private static String getWindowTitle()

/*
     * Opens a modal file chooser dialog that prompts the user to select the output file name.
     *
     * @param visatApp the VISAT application
     * @return the selected file, null means "Cancel"
     */
    private static File promptForFile(String defaultFileName) {
        final SnapFileFilter fileFilter = new SnapFileFilter("TXT", "txt", "Text");
        return Dialogs.requestFileForSave(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), false, fileFilter, ".txt", defaultFileName, null, "exportMaskPixels.lastDir");
    }

    /**
     * Writes all pixel values of the given product within the given Mask to the specified out.
     *
     * @param out      the data output writer
     * @param product  the product providing the pixel values
     * @param maskName the mask name
     * @param mustCreateHeader
     * @param mustExportTiePoints
     * @param mustExportWavelengthsAndSF
     * @param pm       progress monitor
     * @return true for success, false if export has been terminated (by user)
     */
    private static boolean exportMaskPixels(final PrintWriter out, final Product product, String maskName, boolean mustCreateHeader, boolean mustExportTiePoints, boolean mustExportWavelengthsAndSF, ProgressMonitor pm) throws IOException {
        final RenderedImage maskImage = product.getMaskGroup().get(maskName).getSourceImage();
        final int minTileX = maskImage.getMinTileX();
        final int minTileY = maskImage.getMinTileY();
        final int numXTiles = maskImage.getNumXTiles();
        final int numYTiles = maskImage.getNumYTiles();
        final int w = product.getSceneRasterWidth();
        final int h = product.getSceneRasterHeight();
        final Rectangle imageRect = new Rectangle(0, 0, w, h);
        pm.beginTask("Writing pixel data...", numXTiles * numYTiles + 2);
        try {
            if (mustCreateHeader) {
                createHeader(out, product, maskName, mustExportWavelengthsAndSF);
            }
            pm.worked(1);
            writeColumnNames(out, product, maskName, mustExportTiePoints);
            pm.worked(1);
            for (int tileX = minTileX; tileX < minTileX + numXTiles; ++tileX) {
                for (int tileY = minTileY; tileY < minTileY + numYTiles; ++tileY) {
                    if (pm.isCanceled()) {
                        return false;
                    }
                    final Rectangle tileRectangle = new Rectangle(maskImage.getTileGridXOffset() + tileX * maskImage.getTileWidth(), maskImage.getTileGridYOffset() + tileY * maskImage.getTileHeight(), maskImage.getTileWidth(), maskImage.getTileHeight());
                    final Rectangle r = imageRect.intersection(tileRectangle);
                    if (!r.isEmpty()) {
                        Raster maskTile = maskImage.getTile(tileX, tileY);
                        for (int y = r.y; y < r.y + r.height; y++) {
                            for (int x = r.x; x < r.x + r.width; x++) {
                                if (maskTile.getSample(x, y, 0) != 0) {
                                    writeDataLine(out, product, maskName, x, y, mustExportTiePoints);
                                }
                            }
                        }
                    }
                    pm.worked(1);
                }
            }
        } finally {
            pm.done();
        }
        return true;
    }

    private static void createHeader(PrintWriter out, Product product, String maskName, boolean mustExportWavelengthsAndSF) {
        out.write("# Exported mask '" + maskName + "' on " + new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss.mmmmmm").format(new GregorianCalendar().getTime()) + "\n");
        out.write("# Product name: " + product.getName() + "\n");
        if (product.getFileLocation() != null) {
            out.write("# Product file location: " + product.getFileLocation() + "\n");
        }
        out.write("\n");
        if (mustExportWavelengthsAndSF) {
            out.write("# Wavelength:");
            // account for pixel-x, pixel-y, lon, lat columns
            out.write("\t\t\t");
            for (final Band band : product.getBands()) {
                out.print("\t");
                out.print("" + band.getSpectralWavelength());
            }
            out.print("\n");
            out.write("# Solar flux:");
            // account for pixel-x, pixel-y, lon, lat columns
            out.write("\t\t\t");
            for (final Band band : product.getBands()) {
                out.print("\t");
                out.print("" + band.getSolarFlux());
            }
            out.print("\n");
        }
    }

    /*
     * Writes the header line of the dataset to be exported.
     *
     * @param out                     the data output writer
     * @param geoCoding               the product's geo-coding
     * @param bands                   the array of bands to be considered
     * @param mustExportTiePointGrids if tie-point grids shall be considered
     * @param tiePointGrids           the array of tie-point grids to be considered
     */
    private static void writeColumnNames(final PrintWriter out, final Product product, final String maskName, final boolean mustExportTiePointGrids) {
        if (!product.isMultiSize()) {
            out.print("Pixel-X");
            out.print("\t");
            out.print("Pixel-Y");
        } else {
            // Add the mask name to the identification of the pixel
            out.print("Pixel-X." + maskName);
            out.print("\t");
            out.print("Pixel-Y." + maskName);
        }
        if (product.getSceneGeoCoding() != null) {
            out.print("\t");
            out.print("Longitude");
            out.print("\t");
            out.print("Latitude");
        }
        for (final Band band : product.getBands()) {
            out.print("\t");
            out.print(band.getName());
        }
        if (mustExportTiePointGrids) {
            for (final TiePointGrid grid : product.getTiePointGrids()) {
                out.print("\t");
                out.print(grid.getName());
            }
        }
        out.print("\n");
    }

    /**
     * Writes a data line of the dataset to be exported for the given pixel position.
     *
     * @param out                 the data output writer
     * @param product             the product
     * @param maskName            the mask name (where the pixel X and Y where read)
     * @param x                   the current pixel's X coordinate
     * @param y                   the current pixel's Y coordinate
     * @param mustExportTiePoints if tie-point grids shall be exported
     * @throws IOException
     */
    private static void writeDataLine(final PrintWriter out, final Product product, final String maskName, final int x, final int y, final boolean mustExportTiePoints) throws IOException {
        // All the positions computations will be done at the centre of pixel cell
        final PixelPos pixelPosRef = new PixelPos(x + 0.5f, y + 0.5f);
        out.print(String.valueOf(pixelPosRef.x));
        out.print("\t");
        out.print(String.valueOf(pixelPosRef.y));
        final Mask mask = product.getMaskGroup().get(maskName);
        final GeoCoding maskGeocoding = mask.getGeoCoding();
        if (maskGeocoding != null) {
            final GeoPos geoPos = maskGeocoding.getGeoPos(pixelPosRef, null);
            out.print("\t");
            out.print(String.valueOf(geoPos.lon));
            out.print("\t");
            out.print(String.valueOf(geoPos.lat));
        }
        final int[] intPixel = new int[1];
        final float[] floatPixel = new float[1];
        for (final Band band : product.getBands()) {
            out.print("\t");
            PixelPos pixelForBand = product.getPixelForBand(pixelPosRef, mask, band);
            int xForBand = MathUtils.floorInt(pixelForBand.x);
            int yForBand = MathUtils.floorInt(pixelForBand.y);
            if (band.isPixelValid(xForBand, yForBand)) {
                if (band.isFloatingPointType()) {
                    band.readPixels(xForBand, yForBand, 1, 1, floatPixel, ProgressMonitor.NULL);
                    out.print(floatPixel[0]);
                } else {
                    band.readPixels(xForBand, yForBand, 1, 1, intPixel, ProgressMonitor.NULL);
                    out.print(intPixel[0]);
                }
            } else {
                out.print(RasterDataNode.NO_DATA_TEXT);
            }
        }
        // end loop on bands
        if (mustExportTiePoints) {
            for (final TiePointGrid grid : product.getTiePointGrids()) {
                PixelPos pixelForGrid = product.getPixelForBand(pixelPosRef, mask, grid);
                grid.readPixels(MathUtils.floorInt(pixelForGrid.x), MathUtils.floorInt(pixelForGrid.y), 1, 1, floatPixel, ProgressMonitor.NULL);
                out.print("\t");
                out.print(floatPixel[0]);
                if (Float.isNaN(floatPixel[0])) {
                    System.out.println("NaN for " + grid.getName());
                }
            }
        }
        // end loop on Tie Point grids
        out.print("\n");
    }

    /*
     * Computes the total number of pixels within the specified Mask.
     *
     * @param raster   the raster data node
     * @param maskImage the rendered image masking out the Mask
     * @return the total number of pixels in the Mask
     */
    private static long getNumMaskPixels(final RenderedImage maskImage, int sceneRasterWidth, int sceneRasterHeight) {
        final int minTileX = maskImage.getMinTileX();
        final int minTileY = maskImage.getMinTileY();
        final int numXTiles = maskImage.getNumXTiles();
        final int numYTiles = maskImage.getNumYTiles();
        final Rectangle imageRect = new Rectangle(0, 0, sceneRasterWidth, sceneRasterHeight);
        long numMaskPixels = 0;
        for (int tileX = minTileX; tileX < minTileX + numXTiles; ++tileX) {
            for (int tileY = minTileY; tileY < minTileY + numYTiles; ++tileY) {
                final Rectangle tileRectangle = new Rectangle(maskImage.getTileGridXOffset() + tileX * maskImage.getTileWidth(), maskImage.getTileGridYOffset() + tileY * maskImage.getTileHeight(), maskImage.getTileWidth(), maskImage.getTileHeight());
                final Rectangle r = imageRect.intersection(tileRectangle);
                if (!r.isEmpty()) {
                    Raster maskTile = maskImage.getTile(tileX, tileY);
                    for (int y = r.y; y < r.y + r.height; y++) {
                        for (int x = r.x; x < r.x + r.width; x++) {
                            if (maskTile.getSample(x, y, 0) != 0) {
                                numMaskPixels++;
                            }
                        }
                    }
                }
            }
        }
        return numMaskPixels;
    }

    @Override
    public HelpCtx getHelpCtx() {
        return new HelpCtx(HELP_ID);
    }
}


package org.esa.snap.rcp.actions.file.export;

import com.bc.ceres.core.ProgressMonitor;
import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker;
import org.esa.snap.core.datamodel.*;
import org.esa.snap.core.util.SystemUtils;
import org.esa.snap.core.util.io.FileUtils;
import org.esa.snap.core.util.io.SnapFileFilter;
import org.esa.snap.core.util.math.MathUtils;
import org.esa.snap.rcp.SnapApp;
import org.esa.snap.rcp.util.Dialogs;
import org.esa.snap.rcp.util.MultiSizeIssue;
import org.esa.snap.ui.AbstractDialog;
import org.esa.snap.ui.ModalDialog;
import org.esa.snap.ui.SelectExportMethodDialog;
import org.esa.snap.ui.UIUtils;
import org.esa.snap.ui.product.ProductSceneView;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionRegistration;
import org.openide.util.ContextAwareAction;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.GregorianCalendar;

@ActionID(category = "File", id = "org.esa.snap.rcp.actions.file.export.ExportMaskPixelsAction")
@ActionRegistration(displayName = "#CTL_ExportMaskPixelsAction_MenuText", popupText = "#CTL_ExportMaskPixelsAction_PopupText", lazy = false)
@ActionReferences({ @ActionReference(path = "Menu/File/Export/Other", position = 50), @ActionReference(path = "Menu/Raster/Export", position = 100), @ActionReference(path = "Context/ProductSceneView", position = 50) })
@NbBundle.Messages({ "CTL_ExportMaskPixelsAction_MenuText=Mask Pixels", "CTL_ExportMaskPixelsAction_PopupText=Export Mask Pixels", "CTL_ExportMaskPixelsAction_DialogTitle=Export Mask Pixels", "CTL_ExportMaskPixelsAction_ShortDescription=Export Mask Pixels." })
public class ExportMaskPixelsAction extends AbstractAction implements ContextAwareAction, LookupListener, HelpCtx.Provider {

    private static final String HELP_ID = "exportMaskPixels";

    private static final String ERR_MSG_BASE = "Mask pixels cannot be exported:\n";

    private final Lookup.Result<ProductSceneView> result;

    public ExportMaskPixelsAction() {
        this(Utilities.actionsGlobalContext());
    }

    public ExportMaskPixelsAction(Lookup lkp) {
        super(Bundle.CTL_ExportMaskPixelsAction_MenuText());
        putValue("popupText", Bundle.CTL_ExportMaskPixelsAction_PopupText());
        result = lkp.lookupResult(ProductSceneView.class);
        result.addLookupListener(WeakListeners.create(LookupListener.class, this, result));
        setEnabled(false);
    }

    /**
     * Invoked when a command action is performed.
     *
     * @param event the command event
     */
    @Override
    public void actionPerformed(ActionEvent event) {
        ProductSceneView sceneView = SnapApp.getDefault().getSelectedProductSceneView();
        if (sceneView != null) {
            Product product = sceneView.getProduct();
            exportMaskPixels(product);
        }
    }

    @Override
    public Action createContextAwareInstance(Lookup lkp) {
        return new ExportMaskPixelsAction(lkp);
    }

    @Override
    public void resultChanged(LookupEvent le) {
        ProductSceneView sceneView = SnapApp.getDefault().getSelectedProductSceneView();
        boolean enabled = false;
        if (sceneView != null) {
            Product product = sceneView.getProduct();
            enabled = product.getMaskGroup().getNodeCount() > 0;
        }
        setEnabled(enabled);
    }

    // Private implementations for the "export Mask Pixels" command
    /**
     * Performs the actual "export Mask Pixels" command.
     * @param product
     */
    private void exportMaskPixels(Product product) {
        String[] maskNames = product.getMaskGroup().getNodeNames();
        final String maskName;
        if (maskNames.length == 1) {
            maskName = maskNames[0];
        } else {
            JPanel panel = new JPanel();
            BoxLayout boxLayout = new BoxLayout(panel, BoxLayout.X_AXIS);
            panel.setLayout(boxLayout);
            panel.add(new JLabel("Select Mask: "));
            JComboBox<String> maskCombo = new JComboBox<>(maskNames);
            panel.add(maskCombo);
            ModalDialog modalDialog = new ModalDialog(SnapApp.getDefault().getMainFrame(), Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), panel, ModalDialog.ID_OK_CANCEL | ModalDialog.ID_HELP, getHelpCtx().getHelpID());
            if (modalDialog.show() == AbstractDialog.ID_OK) {
                maskName = (String) maskCombo.getSelectedItem();
            } else {
                return;
            }
        }
        final RenderedImage maskImage = product.getMaskGroup().get(maskName).getSourceImage();
        if (maskImage == null) {
            Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + "No Mask image available.");
            return;
        }
        // Compute total number of Mask pixels
        final long numMaskPixels = getNumMaskPixels(maskImage, product.getSceneRasterWidth(), product.getSceneRasterHeight());
        String numPixelsText;
        if (numMaskPixels == 1) {
            numPixelsText = "One Mask pixel will be exported.\n";
        } else {
            numPixelsText = numMaskPixels + " Mask pixels will be exported.\n";
        }
        // Get export method from user
        final String questionText = "How do you want to export the pixel values?\n";
        final JCheckBox createHeaderBox = new JCheckBox("Create header");
        final JCheckBox exportTiePointsBox = new JCheckBox("Export tie-points");
        final JCheckBox exportWavelengthsAndSFBox = new JCheckBox("Export wavelengths + solar fluxes");
        final int method = SelectExportMethodDialog.run(SnapApp.getDefault().getMainFrame(), getWindowTitle(), questionText + numPixelsText, new JCheckBox[] { createHeaderBox, exportTiePointsBox, exportWavelengthsAndSFBox }, getHelpCtx().getHelpID());
        final boolean mustCreateHeader = createHeaderBox.isSelected();
        final boolean mustExportTiePoints = exportTiePointsBox.isSelected();
        final boolean mustExportWavelengthsAndSF = exportWavelengthsAndSFBox

final PrintWriter out;
        final StringBuffer clipboardText;
        final int initialBufferSize = 256000;
        if (method == SelectExportMethodDialog.EXPORT_TO_CLIPBOARD) {
            // Write into string buffer
            final StringWriter stringWriter = new StringWriter(initialBufferSize);
            out = new PrintWriter(stringWriter);
            clipboardText = stringWriter.getBuffer();
        } else if (method == SelectExportMethodDialog.EXPORT_TO_FILE) {
            // Write into file, get file from user
            final File file = promptForFile(createDefaultFileName(product, maskName));
            if (file == null) {
                // Cancel
                return;
            }
            final FileWriter fileWriter;
            try {
                fileWriter = new FileWriter(file);
            } catch (IOException e) {
                Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + "Failed to create file '" + file + "':\n" + e.getMessage());
                // Error
                return;
            }
            out = new PrintWriter(new BufferedWriter(fileWriter, initialBufferSize));
            clipboardText = null;
        } else {
            // Cancel
            return;
        }
        final ProgressMonitorSwingWorker<Exception, Object> swingWorker = new ProgressMonitorSwingWorker<Exception, Object>(SnapApp.getDefault().getMainFrame(), Bundle.CTL_ExportMaskPixelsAction_DialogTitle()) {

            @Override
            protected Exception doInBackground(ProgressMonitor pm) throws Exception {
                Exception returnValue = null;
                try {
                    boolean success = exportMaskPixels(out, product, maskName, mustCreateHeader, mustExportTiePoints, mustExportWavelengthsAndSF, pm);
                    if (success && clipboardText != null) {
                        SystemUtils.copyToClipboard(clipboardText.toString());
                        clipboardText.setLength(0);
                    }
                } catch (Exception e) {
                    returnValue = e;
                } finally {
                    out.close();
                }
                return returnValue;
            }

            @Override
            public void done() {
                // clear status bar
                SnapApp.getDefault().setStatusBarMessage("");
                UIUtils.setRootFrameDefaultCursor(SnapApp.getDefault().getMainFrame());
                // On error, show error message
                Exception exception;
                try {
                    exception = get();
                } catch (Exception e) {
                    exception = e;
                }
                if (exception != null) {
                    Dialogs.showError(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), ERR_MSG_BASE + exception.getMessage());
                }
            }
        };
        // show wait-cursor
        UIUtils.setRootFrameWaitCursor(SnapApp.getDefault().getMainFrame());
        // show message in status bar
        SnapApp.getDefault().setStatusBarMessage("Exporting Mask pixels...");
        // Start separate worker thread.
        swingWorker.execute();
    }

    private static String createDefaultFileName(final Product raster, String maskName) {
        String productName = FileUtils.getFilenameWithoutExtension(raster.getProduct().getName());
        return productName + "_" + maskName + "_Mask.txt";
    }

    private static String getWindowTitle() {
        return SnapApp.getDefault().getInstanceName() + " - " + Bundle.CTL_ExportMaskPixelsAction_DialogTitle();
    }

    /*
     * Opens a modal file chooser dialog that prompts the user to select the output file name.
     *
     * @param visatApp the VISAT application
     * @return the selected file, null means "Cancel"
     */
    private static File promptForFile(String defaultFileName) {
        final SnapFileFilter fileFilter = new SnapFileFilter("TXT", "txt", "Text");
        return Dialogs.requestFileForSave(Bundle.CTL_ExportMaskPixelsAction_DialogTitle(), false, fileFilter, ".txt", defaultFileName, null, "exportMaskPixels.lastDir");
    }

    /**
     * Writes all pixel values of the given product within the given Mask to the specified out.
     *
     * @param out      the data output writer
     * @param product  the product providing the pixel values
     * @param maskName the mask name
     * @param mustCreateHeader
     * @param mustExportTiePoints
     * @param mustExportWavelengthsAndSF
     * @param pm       progress monitor
     * @return true for success, false if export has been terminated (by user)
     */
    private static boolean exportMaskPixels(final PrintWriter out, final Product product, String maskName, boolean mustCreateHeader, boolean mustExportTiePoints, boolean mustExportWavelengthsAndSF, ProgressMonitor pm) throws IOException {
        final RenderedImage maskImage = product.getMaskGroup().get(maskName).getSourceImage();
        final int minTileX = maskImage.getMinTileX();
        final int minTileY = maskImage.getMinTileY();
        final int numXTiles = maskImage.getNumXTiles();
        final int numYTiles = maskImage.getNumYTiles();
        final int w = product.getSceneRasterWidth();
        final int h = product.getSceneRasterHeight();
        final Rectangle imageRect = new Rectangle(0, 0, w, h);
        pm.beginTask("Writing pixel data...", numXTiles * numYTiles + 2);
        try {
            if (mustCreateHeader) {
                createHeader(out, product, maskName, mustExportWavelengthsAndSF);
            }
            pm.worked(1);
            writeColumnNames(out, product, maskName, mustExportTiePoints);
            pm.worked(1);
            for (int tileX = minTileX; tileX < minTileX + numXTiles; ++tileX) {
                for (int tileY = minTileY; tileY < minTileY + numYTiles; ++tileY) {
                    if (pm.isCanceled()) {
                        return false;
                    }
                    final Rectangle tileRectangle = new Rectangle(maskImage.getTileGridXOffset() + tileX * maskImage.getTileWidth(), maskImage.getTileGridYOffset() + tileY * maskImage.getTileHeight(), maskImage.getTileWidth(), maskImage.getTileHeight());
                    final Rectangle r = imageRect.intersection(tileRectangle);
                    if (!r.isEmpty()) {
                        Raster maskTile = maskImage.getTile(tileX, tileY);
                        for (int y = r.y; y < r.y + r.height; y++) {
                            for (int x = r.x; x < r.x + r.width; x++) {
                                if (maskTile.getSample(x, y, 0) != 0) {
                                    writeDataLine(out, product, maskName, x, y, mustExportTiePoints);
                                }
                            }
                        }
                    }
                    pm.worked(1);
                }
            }
        } finally {
            pm.done();
        }
        return true;
    }

    private static void createHeader(PrintWriter out, Product product, String maskName, boolean mustExportWavelengthsAndSF) {
        out.write("# Exported mask '" + maskName + "' on " + new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss.mmmmmm").format(new GregorianCalendar().getTime()) + "\n");
        out.write("# Product name: " + product.getName() + "\n");
        if (product.getFileLocation() != null) {
            out.write("# Product file location: " + product.getFileLocation() + "\n");
        }
        out.write("\n");
        if (mustExportWavelengthsAndSF) {
            out.write("# Wavelength:");
            // account for pixel-x, pixel-y, lon, lat columns
            out.write("\t\t\t");
            for (final Band band : product.getBands()) {
                out.print("\t");
                out.print("" + band.getSpectralWavelength());
            }
            out.print("\n");
            out.write("# Solar flux:");
            // account for pixel-x, pixel-y, lon, lat columns
            out.write("\t\t\t");
            for (final Band band : product.getBands()) {
                out.print("\t");
                out.print("" + band.getSolarFlux());
            }
            out.print("\n");
        }
    }

    /*
     * Writes the header line of the dataset to be exported.
     *
     * @param out                     the data output writer
     * @param geoCoding               the product's geo-coding
     * @param bands                   the array of bands to be considered
     * @param mustExportTiePointGrids if tie-point grids shall be considered
     * @param tiePointGrids           the array of tie-point grids to be considered
     */
    private static void writeColumnNames(final PrintWriter out, final Product product, final String maskName, final boolean mustExportTiePointGrids) {
        if (!product.isMultiSize()) {
            out.print("Pixel-X");
            out.print("\t");
            out.print("Pixel-Y");
        } else {
            // Add the mask name to the identification of the pixel
            out.print("Pixel-X." + maskName);
            out.print("\t");
            out.print("Pixel-Y." + maskName);
        }
        if (product.getSceneGeoCoding() != null) {
            out.print("\t");
            out.print("Longitude");
            out.print("\t");
            out.print("Latitude");
        }
        for (final Band band : product.getBands()) {
            out.print("\t");
            out.print(band.getName());
        }
        if (mustExportTiePointGrids) {
            for (final TiePointGrid grid : product.getTiePointGrids()) {
                out.print("\t");
                out.print(grid.getName());
            }
        }
        out.print("\n");
    }

    /**
     * Writes a data line of the dataset to be exported for the given pixel position.
     *
     * @param out                 the data output writer
     * @param product             the product
     * @param maskName            the mask name (where the pixel X and Y where read)
     * @param x                   the current pixel's X coordinate
     * @param y                   the current pixel's Y coordinate
     * @param mustExportTiePoints if tie-point grids shall be exported
     * @throws IOException
     */
    private static void writeDataLine(final PrintWriter out, final Product product, final String maskName, final int x, final int y, final boolean mustExportTiePoints) throws IOException {
        // All the positions computations will be done at the centre of pixel cell
        final PixelPos pixelPosRef = new PixelPos(x + 0.5f, y + 0.5f);
        out.print(String.valueOf(pixelPosRef.x));
        out.print("\t");
        out.print(String.valueOf(pixelPosRef.y));
        final Mask mask = product.getMaskGroup().get(maskName);
        final GeoCoding maskGeocoding = mask.getGeoCoding();
        if (maskGeocoding != null) {
            final GeoPos geoPos = maskGeocoding.getGeoPos(pixelPosRef, null);
            out.print("\t");
            out.print(String.valueOf(geoPos.lon));
            out.print("\t");
            out.print(String.valueOf(geoPos.lat));
        }
        final int[] intPixel = new int[1];
        final float[] floatPixel = new float[1];
        for (final Band band : product.getBands()) {
            out.print("\t");
            PixelPos pixelForBand = product.getPixelForBand(pixelPosRef, mask, band);
            int xForBand = MathUtils.floorInt(pixelForBand.x);
            int yForBand = MathUtils.floorInt(pixelForBand.y);
            if (band.isPixelValid(xForBand, yForBand)) {
                if (band.isFloatingPointType()) {
                    band.readPixels(xForBand, yForBand, 1, 1, floatPixel, ProgressMonitor.NULL);
                    out.print(floatPixel[0]);
                } else {
                    band.readPixels(xForBand, yForBand, 1, 1, intPixel, ProgressMonitor.NULL);
                    out.print(intPixel[0]);
                }
            } else {
                out.print(RasterDataNode.NO_DATA_TEXT);
            }
        }
        // end loop on bands
        if (mustExportTiePoints) {
            for (final TiePointGrid grid : product.getTiePointGrids()) {
                PixelPos pixelForGrid = product.getPixelForBand(pixelPosRef, mask, grid);
                grid.readPixels(MathUtils.floorInt(pixelForGrid.x), MathUtils.floorInt(pixelForGrid.y), 1, 1, floatPixel, ProgressMonitor.NULL);
                out.print("\t");
                out.print(floatPixel[0]);
                if (Float.isNaN(floatPixel[0])) {
                    System.out.println("NaN for " + grid.getName());
                }
            }
        }
        // end loop on Tie Point grids
        out.print("\n");
    }

    /*
     * Computes the total number of pixels within the specified Mask.
     *
     * @param raster   the raster data node
     * @param maskImage the rendered image masking out the Mask
     * @return the total number of pixels in the Mask
     */
    private static long getNumMaskPixels(final RenderedImage maskImage, int sceneRasterWidth, int sceneRasterHeight) {
        final int minTileX = maskImage.getMinTileX();
        final int minTileY = maskImage.getMinTileY();
        final int numXTiles = maskImage.getNumXTiles();
        final int numYTiles = maskImage.getNumYTiles();
        final Rectangle imageRect = new Rectangle(0, 0, sceneRasterWidth, sceneRasterHeight);
        long numMaskPixels = 0;
        for (int tileX = minTileX; tileX < minTileX + numXTiles; ++tileX) {
            for (int tileY = minTileY; tileY < minTileY + numYTiles; ++tileY) {
                final Rectangle tileRectangle = new Rectangle(maskImage.getTileGridXOffset() + tileX * maskImage.getTileWidth(), maskImage.getTileGridYOffset() + tileY * maskImage.getTileHeight(), maskImage.getTileWidth(), maskImage.getTileHeight());
                final Rectangle r = imageRect.intersection(tileRectangle);
                if (!r.isEmpty()) {
                    Raster maskTile = maskImage.getTile(tileX, tileY);
                    for (int y = r.y; y < r.y + r.height; y++) {
                        for (int x = r.x; x < r.x + r.width; x++) {
                            if (maskTile.getSample(x, y, 0) != 0) {
                                numMaskPixels++;
                            }
                        }
                    }
                }
            }
        }
        return numMaskPixels;
    }

    @Override
    public HelpCtx getHelpCtx() {
        return new HelpCtx(HELP_ID);
    }
}