001 package net.databinder.models;
002
003 import java.util.AbstractList;
004 import java.util.ArrayList;
005 import java.util.List;
006
007 import org.apache.wicket.model.IModel;
008 import org.apache.wicket.model.LoadableDetachableModel;
009
010 /**
011 * Projects a single list into multiple, arbitrarily transformed sublists without replicating
012 * the list structure. A parent list containing sublists is the object wrapped in this model,
013 * while the master list model passed in is held and used internally.
014 * <p>If you need to detach the master list, detach this model and the command will be
015 * passed to the contained master list. Any action that changes the size of the master
016 * list must also detach this model so it can recalculate the sublist count.
017 *
018 * @author Nathan Hamblen
019 */
020 public abstract class SublistProjectionModel extends LoadableDetachableModel {
021 /** Continuous list used to feed this model's sublists. */
022 private IModel master;
023
024 public SublistProjectionModel(IModel master) {
025 this.master = master;
026 }
027
028 /** @return number of sublists */
029 protected abstract int getParentSize();
030
031 /** @return index of master list mapped to parameters */
032 protected abstract int transform(int parentIdx, int sublistIdx);
033
034 /** @return size sublist at given index*/
035 protected abstract int getSize(int parentIdx);
036
037 /**
038 * Breaks the parent list into chunks of the requested size, in the same order as
039 * the parent list.
040 */
041 public static class Chunked extends SublistProjectionModel {
042
043 protected int chunkSize;
044
045 public Chunked(int chunkSize, IModel master) {
046 super(master);
047 this.chunkSize = chunkSize;
048 }
049
050 protected int transform(int parentIdx, int sublistIdx) {
051 return parentIdx * chunkSize + sublistIdx;
052 }
053
054 protected int getSize(int parentIdx) {
055 return Math.min(getMasterList().size() - parentIdx * chunkSize,
056 chunkSize);
057 }
058
059 protected int getParentSize() {
060 return (getMasterList().size() - 1) / chunkSize + 1;
061 }
062 }
063
064 /**
065 * Transposes rows and columns so the list runs top to bottom rather than
066 * left to right.
067 */
068 public static class Transposed extends Chunked {
069
070 public Transposed(int columns, IModel master) {
071 super(columns, master);
072 }
073
074 protected int transform(int parentIdx, int sublistIdx) {
075 return parentIdx + sublistIdx * getParentSize();
076 }
077
078 protected int getSize(int parentIdx) {
079 return (getMasterList().size() - parentIdx - 1) / getParentSize() + 1;
080 }
081
082 }
083
084 protected List getMasterList() {
085 return (List) master.getObject();
086 }
087
088 @Override
089 protected List<List> load() {
090 int rows = getParentSize();
091 List<List> parent = new ArrayList<List>(rows);
092 for (int i = 0; i < rows; i++)
093 parent.add(new ProjectedSublist(i));
094
095 return parent;
096 }
097
098 /**
099 * This is a virtual list, a projection of the master list. Its size and index trasform is
100 * governed by the containing object.
101 */
102 @SuppressWarnings("unchecked")
103 protected class ProjectedSublist extends AbstractList {
104 private int parentIdx;
105
106 public ProjectedSublist(final int parentIdx) {
107 this.parentIdx = parentIdx;
108 }
109
110 @Override
111 public Object get(final int index) {
112 return getMasterList().get(transform(parentIdx, index));
113 }
114
115 @Override
116 public int size() {
117 return getSize(parentIdx);
118 }
119 }
120
121 /**
122 * Detach master list.
123 */
124 @Override
125 protected void onDetach() {
126 master.detach();
127 }
128 }