/**
* 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;
import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.function.UnaryOperator;
import org.apache.hadoop.conf.Configuration;
import com.asakusafw.runtime.directio.DataFormat;
import com.asakusafw.testdriver.core.DataModelDefinition;
import com.asakusafw.testdriver.core.DataModelReflection;
import com.asakusafw.testdriver.core.DataModelSinkFactory;
import com.asakusafw.testdriver.core.DataModelSource;
import com.asakusafw.testdriver.core.DataModelSourceFactory;
import com.asakusafw.testdriver.core.DataModelSourceFilter;
import com.asakusafw.testdriver.core.Difference;
import com.asakusafw.testdriver.core.IteratorDataModelSource;
import com.asakusafw.testdriver.core.ModelTransformer;
import com.asakusafw.testdriver.core.SourceDataModelSource;
import com.asakusafw.testdriver.core.TestContext;
import com.asakusafw.testdriver.core.TestDataToolProvider;
import com.asakusafw.testdriver.core.Verifier;
import com.asakusafw.testdriver.core.VerifierFactory;
import com.asakusafw.testdriver.core.VerifyContext;
import com.asakusafw.testdriver.core.VerifyRuleFactory;
import com.asakusafw.testdriver.hadoop.ConfigurationFactory;
import com.asakusafw.utils.io.Provider;
import com.asakusafw.utils.io.Source;
/**
* An abstract implementation of test driver elements.
* @since 0.7.3
* @version 0.9.1
*/
public abstract class DriverElementBase {
/**
* Returns the caller class.
* @return the caller class
*/
protected abstract Class<?> getCallerClass();
/**
* Returns the test tools.
* @return the test tools
*/
protected abstract TestDataToolProvider getTestTools();
/**
* Converts a source path into {@link DataModelSourceFactory} which provides data models.
* This implementation lazily converts source contents into equivalent {@link DataModelReflection}s.
* @param sourcePath the source path
* @return the {@link DataModelSourceFactory}
* @throws IllegalArgumentException if the target resource is not found
*/
protected final DataModelSourceFactory toDataModelSourceFactory(String sourcePath) {
if (sourcePath == null) {
throw new IllegalArgumentException("sourcePath must not be null"); //$NON-NLS-1$
}
URI sourceUri;
try {
sourceUri = toUri(sourcePath);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(MessageFormat.format(
Messages.getString("DriverElementBase.errorInvalidUri"), //$NON-NLS-1$
sourcePath), e);
}
return getTestTools().getDataModelSourceFactory(sourceUri);
}
/**
* Converts an data model object collection into {@link DataModelSourceFactory} which provides data models.
* This implementation immediately converts data model objects into equivalent {@link DataModelReflection}s.
* @param <T> the data model type
* @param definition the data model definition
* @param sourceObjects the original data model objects
* @return the {@link DataModelSourceFactory}
*/
protected final <T> DataModelSourceFactory toDataModelSourceFactory(
DataModelDefinition<T> definition,
Iterable<? extends T> sourceObjects) {
if (sourceObjects == null) {
throw new IllegalArgumentException("sourceObjects must not be null"); //$NON-NLS-1$
}
ArrayList<DataModelReflection> results = new ArrayList<>();
for (T dataModel : sourceObjects) {
results.add(definition.toReflection(dataModel));
}
results.trimToSize();
return new DataModelSourceFactory() {
@Override
public <S> DataModelSource createSource(
DataModelDefinition<S> inner, TestContext context) throws IOException {
if (inner.getModelClass() != definition.getModelClass()) {
throw new IllegalStateException();
}
return new IteratorDataModelSource(results.iterator());
}
@Override
public String toString() {
return "DataModelSource(Iterable)"; //$NON-NLS-1$
}
};
}
/**
* Converts an data model object collection into {@link DataModelSourceFactory} which provides data models.
* This implementation lazily converts data model objects into equivalent {@link DataModelReflection}s.
* @param <T> the data model type
* @param sourceProvider the original data model objects
* @return the {@link DataModelSourceFactory}
*/
protected final <T> DataModelSourceFactory toDataModelSourceFactory(
Provider<? extends Source<? extends T>> sourceProvider) {
return new DataModelSourceFactory() {
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public DataModelSource createSource(
DataModelDefinition definition,
TestContext context) throws IOException {
try {
return new SourceDataModelSource<>(definition, sourceProvider.open());
} catch (InterruptedException e) {
throw (InterruptedIOException) new InterruptedIOException().initCause(e);
}
}
@Override
public String toString() {
return String.format("DataModelSource(%s)", sourceProvider); //$NON-NLS-1$
}
};
}
/**
* Loads a source file {@link DataModelSourceFactory} which provides data models.
* This implementation immediately converts loaded into {@link DataModelReflection}s.
* @param <T> the data type
* @param definition the data model definition
* @param formatClass the data format class
* @param sourcePath the input file path on the class path
* @return the loaded {@link DataModelSourceFactory}
* @since 0.9.1
*/
protected final <T> DataModelSourceFactory toDataModelSourceFactory(
DataModelDefinition<T> definition,
Class<? extends DataFormat<? super T>> formatClass,
String sourcePath) {
if (definition == null) {
throw new IllegalArgumentException("definition must not be null"); //$NON-NLS-1$
}
if (formatClass == null) {
throw new IllegalArgumentException("formatClass must not be null"); //$NON-NLS-1$
}
if (sourcePath == null) {
throw new IllegalArgumentException("sourcePath must not be null"); //$NON-NLS-1$
}
try {
Configuration conf = ConfigurationFactory.getDefault().newInstance();
URL url = toUri(sourcePath).toURL();
return DirectIoUtil.load(conf, definition, formatClass, url);
} catch (IOException | InterruptedException | URISyntaxException e) {
throw new IllegalArgumentException(MessageFormat.format(
"error occurred while loading Direct I/O input: {0}",
sourcePath), e);
}
}
/**
* Loads a source file {@link DataModelSourceFactory} which provides data models.
* This implementation immediately converts loaded into {@link DataModelReflection}s.
* @param <T> the data type
* @param definition the data model definition
* @param formatClass the data format class
* @param sourceFile the input file
* @return the loaded {@link DataModelSourceFactory}
* @since 0.9.1
*/
protected final <T> DataModelSourceFactory toDataModelSourceFactory(
DataModelDefinition<T> definition,
Class<? extends DataFormat<? super T>> formatClass,
File sourceFile) {
if (definition == null) {
throw new IllegalArgumentException("definition must not be null"); //$NON-NLS-1$
}
if (formatClass == null) {
throw new IllegalArgumentException("formatClass must not be null"); //$NON-NLS-1$
}
if (sourceFile == null) {
throw new IllegalArgumentException("sourceFile must not be null"); //$NON-NLS-1$
}
try {
Configuration conf = ConfigurationFactory.getDefault().newInstance();
return DirectIoUtil.load(conf, definition, formatClass, sourceFile);
} catch (IOException | InterruptedException e) {
throw new IllegalArgumentException(MessageFormat.format(
"error occurred while loading Direct I/O input: {0}",
sourceFile), e);
}
}
/**
* Converts an model transformer into {@link DataModelSourceFilter}.
* @param definition the target data model definition
* @param transformer the data model transformer
* @return the filter which transforms each data model objects using the transformer
* @param <T> the data model type
* @since 0.7.3
*/
protected final <T> DataModelSourceFilter toDataModelSourceFilter(
DataModelDefinition<T> definition,
ModelTransformer<? super T> transformer) {
return new DataModelSourceFilter() {
@Override
public DataModelSource apply(DataModelSource source) {
return new DataModelSource() {
@Override
public DataModelReflection next() throws IOException {
DataModelReflection next = source.next();
if (next == null) {
return null;
}
T object = definition.toObject(next);
transformer.transform(object);
return definition.toReflection(object);
}
@Override
public void close() throws IOException {
source.close();
}
};
}
@Override
public String toString() {
return MessageFormat.format(
"Filter(transformer={0})", //$NON-NLS-1$
transformer);
}
};
}
/**
* Converts an output path to {@link DataModelSinkFactory} to write to the path.
* @param <T> the data type
* @param definition the data model definition
* @param formatClass the data format class
* @param destinationFile the output path
* @return the target sink factory
* @since 0.9.1
*/
protected final <T> DataModelSinkFactory toDataModelSinkFactory(
DataModelDefinition<T> definition,
Class<? extends DataFormat<? super T>> formatClass,
File destinationFile) {
if (definition == null) {
throw new IllegalArgumentException("definition must not be null"); //$NON-NLS-1$
}
if (formatClass == null) {
throw new IllegalArgumentException("formatClass must not be null"); //$NON-NLS-1$
}
if (destinationFile == null) {
throw new IllegalArgumentException("sourceFile must not be null"); //$NON-NLS-1$
}
try {
Configuration conf = ConfigurationFactory.getDefault().newInstance();
return DirectIoUtil.dump(conf, definition, formatClass, destinationFile);
} catch (IOException e) {
throw new IllegalArgumentException(MessageFormat.format(
"error occurred while preparing Direct I/O output: {0}",
destinationFile), e);
}
}
/**
* Converts a pair of expected data set factory and verify rule factory into {@link VerifyRuleFactory}.
* @param verifierFactory the original verifier factory
* @param sourceFilter the filter for verifier input
* @return the {@link VerifierFactory} which provides a verifier using the filtered input
* @since 0.7.0
*/
protected final VerifierFactory toVerifierFactory(
VerifierFactory verifierFactory,
UnaryOperator<DataModelSource> sourceFilter) {
return new VerifierFactory() {
@Override
public <M> Verifier createVerifier(
DataModelDefinition<M> definition,
VerifyContext context) throws IOException {
final Verifier delegate = verifierFactory.createVerifier(definition, context);
return new Verifier() {
@Override
public List<Difference> verify(DataModelSource results) throws IOException {
DataModelSource filtered = sourceFilter.apply(results);
return delegate.verify(filtered);
}
@Override
public void close() throws IOException {
delegate.close();
}
};
}
@Override
public String toString() {
return MessageFormat.format(
"Verifier(verifier={0}, filter={1})", //$NON-NLS-1$
verifierFactory,
sourceFilter);
}
};
}
/**
* Returns URI for the existing resource.
* @param path the path string of the target resource
* @return the URI for the target resource
* @throws URISyntaxException if the URI is not valid
*/
public URI toUri(String path) throws URISyntaxException {
if (path == null) {
throw new IllegalArgumentException("path must not be null"); //$NON-NLS-1$
}
// FIXME for invalid characters
URI uri = URI.create(path.replace('\\', '/'));
if (uri.getScheme() != null) {
return uri;
}
URL url = getCallerClass().getResource(uri.getPath());
if (url == null) {
throw new IllegalArgumentException(MessageFormat.format(
Messages.getString("DriverElementBase.errorMissingResource"), //$NON-NLS-1$
path,
getCallerClass().getName()));
}
URI resourceUri = url.toURI();
if (uri.getFragment() == null) {
return resourceUri;
} else {
URI resolvedUri = URI.create(resourceUri.toString() + '#' + uri.getFragment());
return resolvedUri;
}
}
/**
* Returns a URI for the local resource.
* @param path the local path
* @return the resulting URI
*/
protected URI toOutputUri(String path) {
URI uri = URI.create(path.replace('\\', '/'));
if (uri.getScheme() != null) {
return uri;
}
return new File(path).toURI();
}
}