1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.millscript.millscript.functions;
22
23 import org.millscript.commons.util.EList;
24 import org.millscript.commons.util.EMap;
25 import org.millscript.commons.util.IList;
26 import org.millscript.commons.util.IMap;
27 import org.millscript.commons.util.IMapDefault;
28 import org.millscript.commons.util.ListIterator;
29 import org.millscript.commons.util.MapIterator;
30 import org.millscript.commons.util.defaults.ConstantMapDefault;
31 import org.millscript.commons.util.list.ELinkedList;
32 import org.millscript.commons.util.map.EBinaryTreeMap;
33 import org.millscript.commons.util.map.IEmptyMap;
34 import org.millscript.millscript.datatypes.MapAwareTools;
35 import org.millscript.millscript.vm.Machine;
36
37 /**
38 * This class implements the MillScript <code>group</code> function.
39 */
40 public final class GroupFunction extends Function {
41
42 /**
43 * Returns an empty map with a chain of empty maps as their default values,
44 * up to the required depth. The last map in the chain has a default value
45 * of <code>null</code>.
46 * <p>
47 * For example, if the depth is one, this will return an empty map with a
48 * default value of <code>null</code>. If the depth is two, this will
49 * return an empty map with a default value of an empty map with a default
50 * value of <code>null</code>, etc.
51 * </p>
52 *
53 * @param depth the required depth of map
54 * @return an empty map with a chain of empty maps as their default values
55 * of the specified depth
56 */
57 static IMapDefault makeDefault( final int depth ) {
58 IMap< Object, Object > nullMap = new IEmptyMap< Object, Object >();
59 for ( int i = 1; i < depth; i++ ) {
60 final IMapDefault< Object, Object > mapDefault = new ConstantMapDefault< Object, Object >( nullMap );
61 nullMap = new IEmptyMap< Object, Object >();
62 nullMap.setDefault( mapDefault );
63 }
64 return new ConstantMapDefault< Object, Object >( nullMap );
65 }
66
67 /**
68 * Returns the supplied data grouped by the specified field. The supplied
69 * data is sorted into a Map, whose keys are all the unique values for the
70 * specified field and whose values are the list of data which shares that
71 * unique value. This method is used for single level groupings, i.e.
72 * grouping by a single field.
73 *
74 * <p>
75 * In MillScript we might refer to this as a map-of-lists.
76 * </p>
77 *
78 * @param mc the current machine to perform operations on
79 * @param list a list of objects, with a map view, to group
80 * @param by the object/field to group by
81 * @return a map containing the grouped data, where the keys are the
82 * unique values for the specified field and the values a list
83 */
84 @SuppressWarnings( "unchecked" )
85 static EMap group( final Machine mc, final IList list, final Object by ) {
86
87 EMap result = new EBinaryTreeMap();
88
89 ListIterator< ? > it = list.iterator( true );
90 while ( it.hasNext() ) {
91
92 final Object record = it.nextValue();
93
94
95
96 final Object key = MapAwareTools.get( record, by );
97
98 EList sublist = (EList)result.get( key );
99 if ( sublist == null ) {
100
101 sublist = new ELinkedList();
102 result.insert( key, sublist );
103 }
104
105 sublist.addLast( record );
106 }
107
108 return result;
109 }
110
111 /**
112 * Returns the supplied data grouped by the specified fields. This is used
113 * where the group will have two or more levels. The list of objects that
114 * are to be grouped do not need to be maps, but they must have a map view.
115 *
116 * @param mc the current machine to perform operations on
117 * @param list a list of objects, with a map view, to group
118 * @param fields a list of fields to group the list by
119 * @param idx the index number of the field to group by, which is also
120 * effectively the current level in the group
121 * @param limit the number of levels this deep group will have
122 * @return a map containing the grouped data
123 */
124 @SuppressWarnings( "unchecked" )
125 static IMap deepGroup( final Machine mc, final IList list, final IList fields, int idx, final int limit ) {
126
127
128
129
130
131
132
133 EMap map = group( mc, list, fields.get( idx ) );
134
135
136 MapIterator entries = map.iterator( true );
137
138
139 idx += 1;
140
141 if ( idx < limit ) {
142
143
144 map.setDefault( GroupFunction.makeDefault( limit - idx ) );
145
146 while ( entries.hasNext() ) {
147
148
149 map.insert(
150 entries.nextKey(),
151 deepGroup( mc, (IList) entries.currentValue(), fields, idx, limit )
152 );
153 }
154 }
155 return map;
156 }
157
158 /**
159 * @see org.millscript.millscript.functions.Function#apply(org.millscript.millscript.vm.Machine, int)
160 */
161 @Override
162 public void apply( final Machine mc, final int nargs ) {
163 if ( nargs <= 0 ) {
164 checkNargsGT( mc, 1, nargs );
165 } else if ( nargs == 2 ) {
166 Object by = mc.popObject();
167 IList list = mc.popIList();
168 mc.pushObject( group( mc, list, by ) );
169 } else if ( nargs >= 3 ) {
170
171 EList< Object > fields = new ELinkedList< Object >();
172
173
174
175 for ( int i = 1; i < nargs; i++ ) {
176 fields.addFirst( mc.popObject() );
177 }
178
179 IList list = mc.popIList();
180
181
182 mc.pushObject( deepGroup( mc, list, fields, 1, nargs ) );
183 }
184
185 }
186
187 }