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    }