/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.asakusafw.testdriver.loader;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.stream.Collectors;
import com.asakusafw.runtime.core.GroupView;
import com.asakusafw.testdriver.core.DataModelDefinition;
import com.asakusafw.testdriver.core.DataModelReflection;
import com.asakusafw.testdriver.core.DataModelSource;
import com.asakusafw.testdriver.core.DataModelSourceFactory;
import com.asakusafw.testdriver.core.PropertyName;
import com.asakusafw.testdriver.core.PropertyType;
import com.asakusafw.testdriver.core.TestContext;
/**
* A basic implementation of {@link GroupLoader}.
* @param <T> the data type
* @since 0.9.1
* @see BasicDataLoader
*/
public class BasicGroupLoader<T> implements GroupLoader<T> {
private final TestContext context;
private final DataModelDefinition<T> definition;
private final DataModelSourceFactory factory;
private final List<PropertyName> grouping;
private Comparator<DataModelReflection> refComparator;
BasicGroupLoader(
TestContext context,
DataModelDefinition<T> definition,
DataModelSourceFactory factory,
List<PropertyName> grouping,
Comparator<DataModelReflection> comparator) {
this.context = context;
this.definition = definition;
this.factory = factory;
this.grouping = grouping;
this.refComparator = comparator;
}
@Override
public GroupLoader<T> order(String... terms) {
if (refComparator != null) {
throw new IllegalStateException("order is already defined"); //$NON-NLS-1$
}
refComparator = Util.toComparator(definition, terms);
return this;
}
@Override
public GroupLoader<T> order(Comparator<? super T> comparator) {
if (refComparator != null) {
throw new IllegalStateException("order is already defined"); //$NON-NLS-1$
}
refComparator = Util.toComparator(definition, comparator);
return this;
}
@Override
public GroupView<T> asView() {
Map<List<Object>, List<DataModelReflection>> map = asRefMap();
Map<List<Object>, List<T>> results = new LinkedHashMap<>();
for (Map.Entry<List<Object>, List<DataModelReflection>> entry : map.entrySet()) {
List<Object> key = entry.getKey();
List<DataModelReflection> refs = entry.getValue();
List<T> resolved = new ArrayList<>(refs.size());
for (DataModelReflection ref : refs) {
T object = definition.toObject(ref);
resolved.add(object);
}
results.put(key, resolved);
}
return new MapGroupView<>(definition, grouping, results);
}
private Map<List<Object>, List<DataModelReflection>> asRefMap() {
Map<List<Object>, List<DataModelReflection>> results = new LinkedHashMap<>();
try (DataModelSource source = factory.createSource(definition, context)) {
while (true) {
DataModelReflection ref = source.next();
if (ref == null) {
break;
}
List<Object> key = new ArrayList<>(grouping.size());
for (PropertyName name : grouping) {
Object value = ref.getValue(name);
key.add(value);
}
results.computeIfAbsent(key, k -> new ArrayList<>()).add(ref);
}
} catch (IOException e) {
throw new IllegalStateException(e);
}
if (refComparator != null) {
for (List<DataModelReflection> refs : results.values()) {
refs.sort(refComparator);
}
}
return results;
}
private static class MapGroupView<T> implements GroupView<T> {
private final DataModelDefinition<T> definition;
private final PropertyType[] types;
private final Map<List<Object>, List<T>> entity;
MapGroupView(
DataModelDefinition<T> definition,
List<PropertyName> names,
Map<List<Object>, List<T>> entity) {
this.definition = definition;
this.types = names.stream()
.sequential()
.map(definition::getType)
.toArray(PropertyType[]::new);
this.entity = entity;
}
@Override
public Iterator<T> iterator() {
Iterator<? extends List<T>> partitions = entity.values().iterator();
return new Iterator<T>() {
private Iterator<T> nextPartition;
@Override
public boolean hasNext() {
while (true) {
if (nextPartition == null) {
if (partitions.hasNext()) {
nextPartition = partitions.next().iterator();
} else {
return false;
}
}
if (nextPartition.hasNext()) {
return true;
} else {
nextPartition = null;
}
}
}
@Override
public T next() {
if (nextPartition == null) {
throw new NoSuchElementException();
}
return nextPartition.next();
}
};
}
@Override
public List<T> find(Object... elements) {
List<Object> key = toKey(elements);
return entity.getOrDefault(key, Collections.emptyList());
}
private List<Object> toKey(Object[] elements) {
if (elements.length != types.length) {
throw new IllegalArgumentException(MessageFormat.format(
"invalid key element types: requires ({0})",
Arrays.stream(types)
.map(Enum::name)
.collect(Collectors.joining())));
}
List<Object> key = new ArrayList<>(elements.length);
for (int i = 0; i < elements.length; i++) {
Object value = definition.resolveRawValue(elements[i]);
if (value != null) {
Class<?> expect = types[i].getRepresentation();
Class<?> actual = value.getClass();
if (expect.isAssignableFrom(actual) == false) {
throw new IllegalArgumentException(MessageFormat.format(
"invalid key element type at {0}: required={1}, specified={2}",
i,
types[i].name(),
expect.getSimpleName()));
}
}
key.add(value);
}
return key;
}
}
}