View Javadoc
1   package guru.mikelue.jdut.datagrain;
2   
3   import java.util.ArrayList;
4   import java.util.Arrays;
5   import java.util.Collections;
6   import java.util.HashMap;
7   import java.util.List;
8   import java.util.Map;
9   import java.util.function.Consumer;
10  import java.util.function.Function;
11  import java.util.function.Supplier;
12  import java.util.stream.Collectors;
13  import java.util.stream.Stream;
14  
15  import org.apache.commons.lang3.Validate;
16  
17  import guru.mikelue.jdut.decorate.DataGrainDecorator;
18  
19  /**
20   * Represents the data of rows.<br>
21   *
22   * <h3>Defining data grains</h3>
23   * The simplest by to build a data grain is using {@link #build} method:
24   *
25   * <pre><code class="java">
26   * DataGrain dataGrain = DataGrain.build(
27   *     tableBuilder -&gt; tableBuilder
28   *         .name("tab_name_1"),
29   *     rowsBuilder -&gt; rowsBuilder
30   *         .implicitColumns("col_id", "col_value")
31   *         .addValues(1, "v_1")
32   *         .addValues(2, "v_2")
33   * );
34   * </code></pre>
35   *
36   * <h3>Combining data grains</h3>
37   * The data grains could be combines(sequentially) by {@link #aggregate} method.
38   *
39   * <pre><code class="java">
40   * // dataGrain - Initialized object of DataGrain
41   * // nextDataGrain - The next data grain
42   * dataGrain = dataGrain.aggregate(nextDataGrain);
43   * </code></pre>
44   *
45   * <h3>Decoration</h3>
46   * You may decorates data grain by {@link DataGrainDecorator}.
47   *
48   * <pre><code class="java">
49   * // dataGrain - Initialized object of DataGrain
50   * // myDecorator - Your decorator
51   *
52   * dataGrain = dataGrain.decorate(myDecorator)
53   * </code></pre>
54   *
55   * @see DataGrainDecorator
56   * @see <a target="_blank" href="https://github.com/mikelue/jdata-unit-test/wiki/API-Guideline">API Guideline</a>
57   */
58  public class DataGrain {
59  	private List<DataRow> rows;
60  
61  	/**
62  	 * Builds with setup of table schema and builder of rows.
63  	 *
64  	 * @param tableBuilderConsumer The builder for table schema
65  	 * @param rowsBuilderConsumer The builder for data of rows
66  	 *
67  	 * @return DataGrain object
68  	 */
69  	public static DataGrain build(
70  		Consumer<SchemaTable.Builder> tableBuilderConsumer,
71  		Consumer<RowsBuilder> rowsBuilderConsumer
72  	) {
73  		RowsBuilderImpl rowsBuilder = new RowsBuilderImpl(
74  			SchemaTable.build(tableBuilderConsumer)
75  		);
76  
77  		rowsBuilderConsumer.accept(rowsBuilder);
78  		return new DataGrain(rowsBuilder.toDataRows());
79  	}
80  
81  	/**
82  	 * Constructs this object by list of rows.
83  	 *
84  	 * @param rows The data of rows
85  	 */
86      public DataGrain(List<DataRow> rows)
87  	{
88  		this.rows = Collections.unmodifiableList(rows);
89  		Validate.notNull(rows, "Need viable rows");
90  	}
91  
92  	/**
93  	 * Gets a row by index(starts with "0").
94  	 *
95  	 * @param index The index of row, starts with "0"
96  	 *
97  	 * @return The match data row
98  	 *
99  	 * @see #getNumberOfRows
100 	 */
101 	public DataRow getRow(int index)
102 	{
103 		Validate.inclusiveBetween(0, rows.size() - 1, index, "The index is invalid: [%d]", index);
104 		return rows.get(index);
105 	}
106 
107 	/**
108 	 * Gets number of rows.
109 	 *
110 	 * @return The number of rows
111 	 *
112 	 * @see #getRows
113 	 */
114 	public int getNumberOfRows()
115 	{
116 		return rows.size();
117 	}
118 
119 	/**
120 	 * Gets data of rows.
121 	 *
122 	 * @return The data of rows
123 	 */
124 	public List<DataRow> getRows()
125 	{
126 		return rows;
127 	}
128 
129 	/**
130 	 * Decorators this data grain and generates a new one.
131 	 *
132 	 * @param decorator The decorator to modify this data grain
133 	 *
134 	 * @return The new data grain
135 	 */
136 	public DataGrain decorate(final DataGrainDecorator decorator)
137 	{
138 		final List<DataRow> decoratedRows = new ArrayList<>(rows.size());
139 		rows.forEach(
140 			row -> decoratedRows.add(DataRow.build(
141 				builder -> decorator.decorate(builder),
142 				row
143 			))
144 		);
145 
146 		return new DataGrain(decoratedRows);
147 	}
148 
149 	/**
150 	 * Aggregates another data grain(appending data of current object).
151 	 *
152 	 * @param dataGrain The data grain to be aggregated
153 	 *
154 	 * @return The result data grain
155 	 */
156 	public DataGrain../../../guru/mikelue/jdut/datagrain/DataGrain.html#DataGrain">DataGrain aggregate(DataGrain dataGrain)
157 	{
158 		List<DataRow> result = new ArrayList<>(getRows());
159 		result.addAll(dataGrain.getRows());
160 
161 		return new DataGrain(result);
162 	}
163 
164 	/**
165 	 * Reverses the data grain.
166 	 *
167 	 * @return A new data grain which is reversed(same row of copied data grain)
168 	 */
169 	public DataGrain reverse()
170 	{
171 		List<DataRow> reversedRows = new ArrayList<>(rows);
172 		Collections.reverse(reversedRows);
173 
174 		return new DataGrain(reversedRows);
175 	}
176 }
177 
178 /**
179  * The definition of columns is not relevant to schema of table.
180  */
181 class RowsBuilderImpl implements RowsBuilder {
182 	private final SchemaTable tableSchema;
183 	private final DataField.Factory fieldFactory;
184 	private List<Map<String, DataField<?>>> rows = new ArrayList<>(CollectionUsage.LIST_SIZE_OF_ROWS);
185     private Map<String, SchemaColumn> columns = new HashMap<>(CollectionUsage.HASH_SPACE_OF_COLUMNS);
186     private Map<Integer, SchemaColumn> columnsOfIndexed;
187 
188 	RowsBuilderImpl(SchemaTable newTableSchema)
189 	{
190 		tableSchema = newTableSchema;
191 		fieldFactory = new DataField.Factory(tableSchema);
192 	}
193 
194 	@Override
195 	public RowsBuilder implicitColumns(String... nameOfColumns)
196 	{
197 		columnsOfIndexed = new HashMap<>(CollectionUsage.HASH_SPACE_OF_COLUMNS);
198 
199 		Arrays.asList(nameOfColumns)
200 			.forEach(new Consumer<String>() {
201 				int index = 1;
202 
203 				@Override
204 				public void accept(String columnName)
205 				{
206 					if (!columns.containsKey(columnName)) {
207 						columns.put(
208 							columnName,
209 							SchemaColumn.build(builder -> builder.name(columnName))
210 						);
211 					}
212 
213 					columnsOfIndexed.put(index++, columns.get(columnName));
214 				}
215 			});
216 
217 		return this;
218 	}
219 
220 	@Override
221     public RowsBuilder addValues(Object... valuesOfField)
222 	{
223 		return add(
224 			Stream.of(valuesOfField)
225 				.map(
226 					new Function<Object, DataField<Object>>() {
227 						int index = 1;
228 
229 						@Override
230 						public DataField<Object> apply(Object value)
231 						{
232 							return fieldFactory.composeData(
233 								columnsOfIndexed.get(index++), value
234 							);
235 						}
236 					}
237 				)
238 		);
239 	}
240 	@Override
241     public RowsBuilder addFields(DataField<?>... dataFields)
242 	{
243 		return add(Stream.of(dataFields));
244 	}
245 	@Override
246     public <T> DataField<T> newField(String columnName, T fieldValue)
247 	{
248 		if (!columns.containsKey(columnName)) {
249 			columns.put(columnName, SchemaColumn.build(builder -> builder.name(columnName)));
250 		}
251 
252 		return fieldFactory.composeData(
253 			columns.get(columnName), fieldValue
254 		);
255 	}
256 	@Override
257     public <T> DataField<T> newField(String columnName, Supplier<T> fieldSupplier)
258 	{
259 		if (!columns.containsKey(columnName)) {
260 			columns.put(columnName, SchemaColumn.build(builder -> builder.name(columnName)));
261 		}
262 
263 		return fieldFactory.composeDataSupplier(
264 			columns.get(columnName), fieldSupplier
265 		);
266 	}
267 
268 	private RowsBuilder add(Stream<DataField<?>> dataFields)
269 	{
270 		rows.add(
271 			dataFields
272 				.map(dataField -> dataField.asMapEntry())
273 				.collect(Collectors.toMap(
274 					entry -> entry.getKey(),
275 					entry -> entry.getValue()
276 				))
277 		);
278 
279 		return this;
280 	}
281 
282 	List<DataRow> toDataRows()
283 	{
284 		final List<DataRow> result = new ArrayList<>(rows.size());
285 		rows.forEach(
286 			fieldMap -> result.add(
287 				DataRow.build(builder -> builder
288 					.tableSchema(tableSchema)
289 					.data(fieldMap)
290 				)
291 			)
292 		);
293 
294 		return result;
295 	}
296 }