BiConsumer を比較処理の記述で使う

2つのオブジェクトを比較処理するものが大量にある時、if 文をたくさん書くのが嫌になり if文を隠ぺいして差がある時の処理を
Consumer と Biconsumer で書くことを考えた。

public static <T, U> boolean parse(T t, U u, Consumer<T> delete, Consumer<U> add, BiConsumer<T, U> modified){
  if (t==null || "".equals(t)){
     if (u != null && !"".equals(u)){
        add.accept(u);
        return true;
      }
   }else{
      if (u==null || "".equals(u)){
         delete.accept(t);
         return true;
      }else{
         if (!t.equals(u)){
            modified.accept(t, u);
            return true;
         }
      }
   }
   return false;
}

Tは、比較元、Uは比較先で、delete は削除時の Consumer 、add は追加時の Consumer
modified は変更時の BiConsumer である。

すると、こんな風に書ける。

boolean res = Difference.parse(old_str, new_str, o->{
    System.out.println("削除:" + o);
}, n->{
    System.out.println("追加:" + n);
}, (o, n)->{
    System.out.println("変更:" + o + " → " + n);
});

Stream でインデックスを求める

リストに存在するオブジェクトが何番目=インデックスを求める方法、Java8 Stream で求める。


見つからない場合は、-1 が返れば良しとする。という要求で、サンプルを書くと。。。

List<String> list = Arrays.asList("a", "b", "c", "d", "e", "f");

/* このリストで、"c" のインデックスを求める。 */

int index = IntStream.range(0, list.size()).filter(i->list.get(i).equals("c")).findAny().orElse(-1);


System.out.println("Index = " + index );

結果は、

Index = 2

になる。

ちょっとこの方法が気に入らなくなったので、Integer 型で、見つからない場合は、null というルールで書き直す。

AtomicInteger atomicInt = new AtomicInteger(-1);

Integer index = list.stream().peek(e->atomicInt.incrementAndGet())
  .filter(e->e.equals("c")).findFirst().map(e->atomicInt.get()).orElse(null);

peek メソッドは前から何につかえばよいのだろうと思っていたが、こういう時に使うといいみたいだ。

CSS の nth-child

CSS の nth-child の書き方、解ってるんだけど、ちょっとパターンが複雑になると

迷うことがあるのでメモ

td:nth-child(odd){ background-color: #ffffcc; } 奇数

1 2 3 4 5 6 7 8 9 10 11

td:nth-child(even){ background-color: #ffffcc; } 偶数

1 2 3 4 5 6 7 8 9 10 11

td:nth-child(n+3){ background-color: #ffffcc; } 3番目~

1 2 3 4 5 6 7 8 9 10 11

td:nth-child(-n+6){ background-color: #ffffcc; } 6番目まで

1 2 3 4 5 6 7 8 9 10 11

td:nth-child(3n+2){ background-color: #ffffcc; }
 2番目から3個目を繰り返す

1 2 3 4 5 6 7 8 9 10 11

td:nth-child(n+3):nth-child(-n+6){ background-color: #ffffcc; }
 3番目~6番目まで

1 2 3 4 5 6 7 8 9 10 11

td:last-child{ background-color: #ffffcc; } 最後

1 2 3 4 5 6 7 8 9 10 11

td:last-child(2){ background-color: #ffffcc; } 最後から2個目

1 2 3 4 5 6 7 8 9 10 11

さらに、nth-child は、繰り返しパターンで float の clear を効かせる方法がある。

ul{   width: 800px; }
li {
     float: left;
     list-style: none;
     text-align: center;
     width: 100px;
     margin: 10px;
    border: 1px solid #808080;
}
/* 3個並んだら→折り返す */
li:nth-child(3n+1){
    clear: both;
}

とリストのスタイルを用意してアイコン(Web-font アイコンでもなんでもいい)

を書いて、、

<ul class="sample">
<li><span class="icon-home"></span></li>
<li><span class="icon-folder"></span></li>
<li><span class="icon-folder-open"></span></li>
<li><span class="icon-folder-download"></span></li>
</ul>

f:id:posturan:20160619113202j:plain

4個目は折り返す。table の表を li タグで書くなら全体幅を決めて nth-child で

clear: both を効かせる。プログラムで動的に表を書くならこの方法も1つの良い方法だ。

 

Wicket ListView のイテレータ を無理やり Stream で処理してみる

Wicket ListView が提供する itarator メソッドは、以下のような使い方もできる。

無理やり、イテレータを Stream に変換して処理してみた。

final WebMarkupContainer listViewContainer = new WebMarkupContainer("listview_container");
listViewContainer.setOutputMarkupId(true);
queue(listViewContainer);
queue(new ListView<ReservDevice>("listview", new PropertyModel<List<Foo>>(this, "fooList")){
	@Override
	protected void populateItem(final ListItem<ReservDevice> item){
		item.add(new TextField<String>("user_name", new Model<>());
		item.add(new AjaxButton("check"){
			restuructFunction();
			target.add(listViewContainer)
		}):
	}
	// リストイテレータで処理する
	private void restuructFunction(){
		// リストをクリアして入れ直す。
		fooList.clear();
		
		StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator(), Spliterator.ORDERED), false).forEach(i->{
			Foo f = new Foo();
			f.userName = ((TextField<String>)i.get("user_name")).getModelObject();
			fooList.add(f);
		});
	}
});

AjaxTabbedPanel 内に配置した入力フィールド値をタブ切り替え時に受け取る

Wicket AjaxTabbedPanel を使った時に、タブの中に配置した入力フィールドの値をフォーム送信ボタンによる受信ではなく、
タブのクリック=タブ切り替えのタイミングで受け取る方法に悩んだ。

org.apache.wicket.extensions.markup.html.tabs.TabbedPanel の継承である AjaxTabbedPanel が、AjaxFallbackLink でタブのリンクを
作っているので、これを AjaxSubmitLink での実装にするものを自分で用意する。

当然、Form コンポーネントをタブを配置する親の方で配置する必要がある。

import java.util.List;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.extensions.ajax.markup.html.tabs.AjaxTabbedPanel;
import org.apache.wicket.extensions.markup.html.tabs.ITab;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.model.IModel;
/**
 * AjaxTabbedPanelをAjaxSubmitLinkで実装
 */
public class YAjaxTabbedPanel<T extends ITab> extends AjaxTabbedPanel<T>{

	public YAjaxTabbedPanel(String id, List tabs){
		super(id, tabs);
	}
	public YAjaxTabbedPanel(String id, List tabs, IModel model){
		super(id, tabs, model);
	}
	@Override
	protected WebMarkupContainer newLink(final String linkId, final int index){
		return new AjaxSubmitLink(linkId){
			@Override
			protected void onSubmit(AjaxRequestTarget target, Form<?> form){
				onBeforeChangeTab(target, index);
				setSelectedTab(index);
				if (target != null){
					target.add(YAjaxTabbedPanel.this);
				}
				onAjaxUpdate(target);
			}
		};
	}
	protected void onBeforeChangeTab(final AjaxRequestTarget target, final int index){
	}
}

これで幸せになれるかもしれない。

Apache POI Excel 入力規則の読み込み(2)

Apache POI Excel の入力規則を参照するのに、前の記事、
Apache POI Excel入力規則の読み込み(1) - Oboe吹きプログラマの黙示録
このように毎回書いてはいられない。

そこで、XSSFSheetオブジェクトを渡して後から何回でも問い合わせするクラスを書いてみた。
プルダウン-リストによる Excel 入力規則 という限定したものなる。プルダウンリストの入力リストが記述されるのは、必ず縦方向、横方向のどちらかになる。
リストの入力規則の時、Excel は、エリア指定で入力リストはないはずだ。

public final class ExcelListValidaionParser{
	private XSSFSheet sheet;
	private Map<String, XSSFDataValidation> cellToValidateKeyMap;
	private Map<String, List<String>> cellToExplictMap;

	public ExcelListValidaionParser(XSSFSheet sheet){
		this.sheet = sheet;

		cellToValidateKeyMap = sheet.getDataValidations().stream().filter(e->e.getValidationConstraint()
		.getValidationType()==ValidationType.LIST)
		.collect(()->new HashMap<String, XSSFDataValidation>(), (r, validation)->{
			List<String> cellnameList = Arrays.stream(validation.getRegions().getCellRangeAddresses())
			.collect(()->new ArrayList<String>(), (a, b)->{
				for(int row=b.getFirstRow();row <= b.getLastRow(); row++){
					for(int col=b.getFirstColumn();col <= b.getLastColumn(); col++){
						a.add(sheet.getRow(row).getCell(col).getReference());
					}
				}
			}, (a, b)->{ a.addAll(b); });
			cellnameList.stream().distinct().forEach(s->r.put(s, validation));
		}, (r, t)->r.putAll(t));

		cellToExplictMap = cellToValidateKeyMap.keySet().stream()
		.collect(()->new HashMap<String, List<String>>(), (r, s)->{
			String[] exp = cellToValidateKeyMap.get(s).getValidationConstraint().getExplicitListValues();
			String[] ary = exp[0].replaceAll("\\$", "").split(":");
			int startRow = Integer.parseInt(ary[0].replaceAll("[A-Z]+", "")) - 1;
			int endRow   = Integer.parseInt(ary[1].replaceAll("[A-Z]+", "")) - 1;
			int startCol = CellReference.convertColStringToIndex(ary[0].replaceAll("[0-9]+", ""));
			int endCol   = CellReference.convertColStringToIndex(ary[1].replaceAll("[0-9]+", ""));
			if (startRow==endRow){
				r.put(s, IntStream.rangeClosed(startCol, endCol).mapToObj(i->{
						XSSFCell cell = sheet.getRow(startRow).getCell(i);
						if (cell.getCellType()==Cell.CELL_TYPE_NUMERIC){
							Integer data = new Double(cell.getNumericCellValue()).intValue();
							return data.toString();
						}else if(cell.getCellType()==Cell.CELL_TYPE_STRING){
							return cell.getStringCellValue();
						}
						return "";
					}).collect(Collectors.toList())
				);
			}else{
				r.put(s, IntStream.rangeClosed(startRow, endRow).mapToObj(i->{
						XSSFCell cell = sheet.getRow(i).getCell(startCol);
						if (cell.getCellType()==Cell.CELL_TYPE_NUMERIC){
							Integer data = new Double(cell.getNumericCellValue()).intValue();
							return data.toString();
						}else if(cell.getCellType()==Cell.CELL_TYPE_STRING){
							return cell.getStringCellValue();
						}
						return "";
					}).collect(Collectors.toList())
				);
			}
		}, (r, v)->r.putAll(v));
	}

	public List<String> getValidationStringList(String cellstring){
		return cellToExplictMap.containsKey(cellstring) ? cellToExplictMap.get(cellstring) : new ArrayList();
	}
	/**
	 * 入力規則のセルへ格納しようとする値が格納可能であるか否かを返す。
	 * @param cellstring セル名 'A1'書式
	 * @param checkValue 格納しようとする値
	 * @return true=可能、入力規則が無い時も可能
	 */
	public boolean acceptValue(String cellstring, String checkValue){
		List<String> list = getValidationStringList(cellstring);
		if (list.size()==0) return true;
		return list.stream().filter(e->e.equals(checkValue)).findFirst().orElse(null) != null;
	}

	public DataValidationConstraint getDataValidationConstraint(String cellstring){
		XSSFCell cell = sheet.getRow(Integer.parseInt(cellstring.replaceAll("[A-Za-z]+", ""))-1)
		.getCell(CellReference.convertColStringToIndex(cellstring.replaceAll("[0-9]+", "")));
		XSSFDataValidation validation = cellToValidateKeyMap.get(cell.getReference());
		if (validation==null) return null;
		return validation.getValidationConstraint();
	}
}
||

Apache POI Excel入力規則の読み込み(1)

Apache POI で Excel の入力規則によるプルダウンの情報を読み取る場合、同じシート内のセルで入力規則の値を格納していないと読み取れない。

f:id:posturan:20160521212235j:plain

f:id:posturan:20160521212317j:plain

このように同じシート内にある入力規則の情報を参照するコードサンプルを書いてみた。
(ただし、このサンプルは縦方向リストのみ)

try(InputStream is = new FileInputStream("template.xlsx");){
	XSSFWorkbook book = new XSSFWorkbook(is);
	XSSFSheet sheet = book.getSheetAt(0);
	/* List<XSSFDataValidation> を取得して展開  */
	sheet.getDataValidations().stream().forEach(e->{
		// CellRangeAddress[] を取得して入力規則適用されてるセルのリファレンスを参照
		Arrays.stream(e.getRegions().getCellRangeAddresses()).forEach(r->{
			String region_start_cellrefer = sheet.getRow(r.getFirstRow()).getCell(r.getFirstColumn()).getReference();
			String region_end_cellrefer = sheet.getRow(r.getLastRow()).getCell(r.getLastColumn()).getReference();
			System.out.println("対象セル:" + region_start_cellrefer + " - " + region_end_cellrefer );
		});
		// 入力時のタイトルとメッセージ
		System.out.println("title = "+ e.getPromptBoxTitle() );
		System.out.println("text  = "+ e.getPromptBoxText() );
		// 入力規則 DataValidationConstraint を取得して、入力規則リスト格納領域を getExplicitListValues() で取得して参照
		Arrays.stream(e.getValidationConstraint().getExplicitListValues()).forEach(p->{
			System.out.println("Excel の 入力規則リスト値格納セル:" +p);
			String[] ary = p.replaceAll("\\$", "").split(":");
			int startRow = Integer.parseInt(ary[0].replaceAll("[A-Z]+", "")) - 1;
			int endRow   = Integer.parseInt(ary[1].replaceAll("[A-Z]+", "")) - 1;
			int startCol = CellReference.convertColStringToIndex(ary[0].replaceAll("[0-9]+", ""));
			int endCol   = CellReference.convertColStringToIndex(ary[1].replaceAll("[0-9]+", ""));
			if (startRow==endRow){
				IntStream.rangeClosed(startCol, endCol).forEach(i->{
					XSSFCell cell = sheet.getRow(startRow).getCell(i);
					if (cell.getCellType()==Cell.CELL_TYPE_NUMERIC){
						Integer data = new Double(cell.getNumericCellValue()).intValue();
						System.out.println(data);
					}else if(cell.getCellType()==Cell.CELL_TYPE_STRING){
						String data = cell.getStringCellValue();
						System.out.println(data);
					}
				});
			}else{
				IntStream.rangeClosed(startRow, endRow).forEach(i->{
					XSSFCell cell = sheet.getRow(i).getCell(startCol);
					if (cell.getCellType()==Cell.CELL_TYPE_NUMERIC){
						Integer data = new Double(cell.getNumericCellValue()).intValue();
						System.out.println(data);
					}else if(cell.getCellType()==Cell.CELL_TYPE_STRING){
						String data = cell.getStringCellValue();
						System.out.println(data);
					}
				});
			}
		});
		System.out.println("------------------------------------");
	});
}catch(Exception e){
	e.printStackTrace();
}

結果としては、以下のような標準出力結果が得られる。

対象セル:D5 - D5
title = タイトル1
text = メッセージ1
Excel の 入力規則リスト値格納セル:$L$2:$L$6
A
B
C
D
E
'--------------------------------'
対象セル:E5 - E5
対象セル:G5 - G5
title = タイトル2
text = メッセージ2
Excel の 入力規則リスト値格納セル:$M$2:$M$6
100
200
300
400
500
'--------------------------------'