/*GroupsModelArray.java
Purpose:
Description:
History:
Sep 3, 2008 9:50:12 AM 2008, Created by Dennis.Chen
Copyright (C) 2007 Potix Corporation. All Rights Reserved.
{{IS_RIGHT
This program is distributed under LGPL Version 2.1 in the hope that
it will be useful, but WITHOUT ANY WARRANTY.
}}IS_RIGHT
*/
package org.zkoss.zul;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import org.zkoss.lang.Objects;
import org.zkoss.util.ArraysX;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.util.ComponentCloneListener;
import org.zkoss.zul.event.GroupsDataEvent;
import org.zkoss.zul.ext.GroupsSortableModel;
/**
* An array implementation of {@link GroupsModel}.
* This implementation takes a list of elements that are not grouped yet,
* and a comparator that will be used to group them.
* The c supports regroup array to groups depends on {@link Comparator} and {@link GroupComparator}.
* For immutable content (no re-grouping allowed), please use {@link SimpleGroupsModel} instead.
*
* <p>Generics:
* <dl>
* <dt>D</dt><dd>The class of each data</dd>
* <dt>H</dt><dd>The class of each group header</dd>
* <dt>F</dt><dd>The class of each group footer</dd>
* <dt>E</dt><dd>The class of each selection. It is the common base class
* of D, H, F. In other words, D, H and F must extend from E.</dd>
* </dl>
* <p>For more information, please refer to
* <a href="http://books.zkoss.org/wiki/ZK_Developer%27s_Reference/MVC/Model/Groups_Model">ZK Developer's Reference: Groups Model</a>
* <p>By default, the model support cloneable when the component is cloned. (since 6.0.0)
* @author Dennis.Chen
* @since 5.0.5
* @see GroupsModel
* @see SimpleGroupsModel
* @see GroupComparator
* @see ComponentCloneListener
*/
public class GroupsModelArray<D, H, F, E> extends AbstractGroupsModel<D, H, F, E>
implements GroupsSortableModel<D>, ComponentCloneListener, Cloneable {
/**
* member field to store native (original) array data
*/
protected D[] _nativedata;
/**
* member field to store Comparator for initial grouping.
*/
protected Comparator<D> _comparator;
/**
* member field to store group data
*/
protected D[][] _data;
/**
* member field to store group head data (generated in {@link #organizeGroup})
*/
protected Object[] _heads;
/**
* member field to store group foot data (generated in {@link #organizeGroup})
*/
protected Object[] _foots;
/**
* member field to store group close status
*/
protected boolean[] _opens;
/**
* Constructor with an array of data.
* @param data an array data to be grouping.
* @param cmpr a comparator implementation help group the data. you could implements {@link GroupComparator} to do more grouping control.<br/>
* At 1st phase, it calls {@link Comparator#compare(Object, Object)} or {@link GroupComparator#compareGroup(Object, Object)} to sort the data.<br/>
* At 2nd phase, it calls {@link Comparator#compare(Object, Object)} or {@link GroupComparator#compareGroup(Object, Object)} to decide which data belong to which group.
* In this phase it also invoke {@link #createGroupHead(Object[], int, int)} and {@link #createGroupFoot(Object[], int, int)} to create head of foot Object of each group.<br/>
* At 3rd phase, it calls {@link Comparator#compare(Object, Object)} to sort data in each group.<br/>
*/
public GroupsModelArray(D[] data, Comparator<D> cmpr) {
this(data, cmpr, 0);
}
/**
* Constructor with an array of data.
* It is the same as GroupsModelArray(data, cmpr, col, true), i.e.,
* <code>data</code> will be cloned first, so <code>data</code>'s content
* won't be changed.
* @param data an array data to be grouping.
* @param cmpr a comparator implementation help group the data. you could implements {@link GroupComparator} to do more grouping control.<br/>
* At 1st phase, it calls {@link Comparator#compare(Object, Object)} or {@link GroupComparator#compareGroup(Object, Object)} to sort the data.<br/>
* At 2nd phase, it calls {@link Comparator#compare(Object, Object)} or {@link GroupComparator#compareGroup(Object, Object)} to decide which data belong to which group.
* In this phase it also invoke {@link #createGroupHead(Object[], int, int)} and {@link #createGroupFoot(Object[], int, int)} to create head of foot Object of each group.<br/>
* At 3rd phase, it calls {@link Comparator#compare(Object, Object)} to sort data in each group.<br/>
* @param col column index associate with cmpr.
*/
public GroupsModelArray(D[] data, Comparator<D> cmpr, int col) {
this(data, cmpr, col, true); //clone
}
/**
* Constructor with an array of data.
* @param data an array data to be grouping.
* @param cmpr a comparator implementation help group the data. you could implements {@link GroupComparator} to do more grouping control.<br/>
* At 1st phase, it calls {@link Comparator#compare(Object, Object)} or {@link GroupComparator#compareGroup(Object, Object)} to sort the data.<br/>
* At 2nd phase, it calls {@link Comparator#compare(Object, Object)} or {@link GroupComparator#compareGroup(Object, Object)} to decide which data belong to which group.
* In this phase it also invoke {@link #createGroupHead(Object[], int, int)} and {@link #createGroupFoot(Object[], int, int)} to create head of foot Object of each group.<br/>
* At 3rd phase, it calls {@link Comparator#compare(Object, Object)} to sort data in each group.<br/>
* @param col column index associate with cmpr.
* @param clone whether to clone <code>data</code>. If not cloning,
* data's content will be changed.
* @since 5.0.6
*/
public GroupsModelArray(D[] data, Comparator<D> cmpr, int col, boolean clone) {
if (data == null || cmpr == null)
throw new IllegalArgumentException("null parameter");
_nativedata = clone ? (D[]) ArraysX.duplicate(data) : data;
_comparator = cmpr;
group(_comparator, true, col);
}
/** @deprecated As of release 6.0.1, there is no way to instantiate
* the array with the correct type.
*/
@SuppressWarnings("unchecked")
public GroupsModelArray(List<D> data, Comparator cmpr, int col) {
this((D[]) data.toArray(), cmpr, col, false); //no need to clone
}
/** @deprecated As of release 6.0.1, there is no way to instantiate
* the array with the correct type.
*/
public GroupsModelArray(List<D> data, Comparator cmpr) {
this(data, cmpr, 0);
}
public D getChild(int groupIndex, int index) {
return _data[groupIndex][index];
}
public int getChildCount(int groupIndex) {
return _data[groupIndex].length;
}
@SuppressWarnings("unchecked")
public H getGroup(int groupIndex) {
return (H) _heads[groupIndex];
}
public int getGroupCount() {
return _data.length;
}
@SuppressWarnings("unchecked")
public F getGroupfoot(int groupIndex) {
return (F) _foots[groupIndex];
}
public boolean hasGroupfoot(int groupIndex) {
return _foots == null ? false : _foots[groupIndex] != null;
}
public void sort(Comparator<D> cmpr, boolean ascending, int col) {
sortAllGroupData(cmpr, ascending, col);
fireEvent(GroupsDataEvent.STRUCTURE_CHANGED, -1, -1, -1);
}
public void group(final Comparator<D> cmpr, boolean ascending, int col) {
Comparator<D> cmprx;
if (cmpr instanceof GroupComparator) {
cmprx = new Comparator<D>() {
public int compare(D o1, D o2) {
return ((GroupComparator<D>) cmpr).compareGroup(o1, o2);
}
};
} else {
cmprx = cmpr;
}
sortDataInGroupOrder(cmprx, ascending, col); //use comparator from constructor to sort native data
organizeGroup(cmprx, col);
if (cmprx != cmpr)
sortAllGroupData(cmpr, ascending, col); //sort by original comparator
fireEvent(GroupsDataEvent.GROUPS_RESET, -1, -1, -1);
}
/**
* @deprecated As of release 6.0.0, replace with {@link #isGroupOpened(int)}
*/
public boolean isClose(int groupIndex) {
return !isGroupOpened(groupIndex);
}
/**
* @deprecated As of release 6.0.0, replace with {@link #addOpenGroup(int)}
* and {@link #removeOpenGroup(int)}.
*/
public void setClose(int groupIndex, boolean close) {
setOpenGroup0(groupIndex, !close);
}
public boolean addOpenGroup(int groupIndex) {
return setOpenGroup0(groupIndex, true);
}
public boolean removeOpenGroup(int groupIndex) {
return setOpenGroup0(groupIndex, false);
}
private boolean setOpenGroup0(int groupIndex, boolean open) {
if (_opens == null) {
if (open)
return true; // _opens == null means all open
int length = getGroupCount();
_opens = new boolean[length];
for (int i = 0; i < length; i++)
_opens[i] = true;
}
if (_opens[groupIndex] != open) {
_opens[groupIndex] = open;
fireEvent(GroupsDataEvent.GROUPS_OPENED, groupIndex, groupIndex, groupIndex);
return true;
}
return false;
}
public boolean isGroupOpened(int groupIndex) {
return _opens == null || _opens[groupIndex];
}
/**
* Sorts data in each group, the group order will not change. invoke this method doesn't fire event.
*/
private void sortAllGroupData(Comparator<D> cmpr, boolean ascending, int col) {
for (int i = 0; i < _data.length; i++) {
sortGroupData(getGroup(i), _data[i], cmpr, ascending, col);
}
}
/**
* Sorts data within a group. Notice that this method doesn't fire event.
* <p>There are three steps to re-group data:
* {@link #sortDataInGroupOrder}, {@link #organizeGroup} and then
* {@link #sortGroupData}.
*
* <p>It is the last step of grouping. It sorts data in the specified
* group.
*/
protected void sortGroupData(H group, D[] groupdata, Comparator<D> cmpr, boolean ascending, int col) {
Arrays.sort(groupdata, cmpr);
}
/**
* Organizes groups based sorted data.
*
* <p>There are three steps to re-group data:
* {@link #sortDataInGroupOrder}, {@link #organizeGroup} and then
* {@link #sortGroupData}.
*
* <p>It is the second step of grouping. It creates group data
* based on the data sorted in the group order by
* {@link #sortDataInGroupOrder}.
*
* @param cmpr the comparator used to compare data in the group order.
* Notice that the comparator is never an instance of {@link GroupComparator}.
* The implementation just uses {@link Comparator#compare} to sort
* the data.
* @param col column index
*/
@SuppressWarnings("unchecked")
protected void organizeGroup(Comparator<D> cmpr, int col) {
List<List<D>> group = new LinkedList<List<D>>();
List<D> gdata = null;
D last = null;
D curr = null;
//regroup native
for (int i = 0; i < _nativedata.length; i++) {
curr = _nativedata[i];
if (last == null || cmpr.compare(last, curr) != 0) {
gdata = new LinkedList<D>();
group.add(gdata);
}
gdata.add(curr);
last = _nativedata[i];
}
//prepare data,head & foot
List<D>[] gd = new List[group.size()];
group.toArray(gd);
Class<?> classD = _nativedata.getClass().getComponentType();
_data = (D[][]) Array.newInstance(classD, gd.length, 0); //new D[gd.length][];
_foots = new Object[gd.length];
_heads = new Object[gd.length];
_opens = new boolean[_data.length];
for (int i = 0; i < gd.length; i++) {
gdata = gd[i];
_data[i] = (D[]) Array.newInstance(classD, gdata.size());
gdata.toArray(_data[i]);
_heads[i] = createGroupHead(_data[i], i, col);
_foots[i] = createGroupFoot(_data[i], i, col);
_opens[i] = createGroupOpen(_data[i], i, col);
}
}
/**
* create group head Object, default implementation return first element of groupdata.
* you can override this method to return your Object.
* @param groupdata data the already in a group.
* @param index group index
* @param col column to group
*/
@SuppressWarnings("unchecked")
protected H createGroupHead(D[] groupdata, int index, int col) {
final D o = groupdata[0];
return o != null && o.getClass().isArray() && col < Array.getLength(o) ? (H) Array.get(o, col) : (H) o;
}
/**
* create group foot Object, default implementation return null, which means no foot .
* you can override this method to return your Object.
* @param groupdata data the already in a group.
* @param index group index
* @param col column to group
*/
protected F createGroupFoot(D[] groupdata, int index, int col) {
return null;
}
/**
* Sorts the native data in the group order.
* After sorted, all data in the first group shall be placed in front
* of the second group, and so on.
*
* <p>There are three steps to re-group data:
* {@link #sortDataInGroupOrder}, {@link #organizeGroup} and then
* {@link #sortGroupData}.
*
* @param cmpr the comparator used to compare data in the group order.
* Notice that the comparator is never an instance of {@link GroupComparator}.
* The implementation just uses {@link Comparator#compare} to sort
* the data.
*/
protected void sortDataInGroupOrder(Comparator<D> cmpr, boolean ascending, int colIndex) {
Arrays.sort(_nativedata, cmpr);
}
/**
* create group open status, default implementation return true, which means open the group.
* you can override this method to return your group open status.
* @param groupdata data the already in a group.
* @param index group index
* @param col column to group
* @since 6.0.0
*/
protected boolean createGroupOpen(D[] groupdata, int index, int col) {
return true;
}
@SuppressWarnings("unchecked")
public Object clone() {
GroupsModelArray clone = (GroupsModelArray) super.clone();
if (_nativedata != null)
clone._nativedata = ArraysX.duplicate(_nativedata);
if (_data != null)
clone._data = ArraysX.duplicate(_data);
if (_heads != null)
clone._heads = ArraysX.duplicate(_heads);
if (_foots != null)
clone._foots = ArraysX.duplicate(_foots);
if (_opens != null)
clone._opens = (boolean[]) ArraysX.duplicate(_opens);
return clone;
}
/**
* Allows the model to clone
* @since 6.0.0
*/
public Object willClone(Component comp) {
return clone();
}
//Object//
public boolean equals(Object o) {
if (this == o)
return true;
if (o instanceof GroupsModelArray) {
return Arrays.equals(_nativedata, ((GroupsModelArray) o)._nativedata);
}
return false;
}
public int hashCode() {
return Arrays.hashCode(_nativedata);
}
public String toString() {
return Objects.toString(_nativedata);
}
}