/**
* 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.dmdl;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.rules.TestName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.asakusafw.dmdl.analyzer.DmdlAnalyzer;
import com.asakusafw.dmdl.analyzer.DmdlSemanticException;
import com.asakusafw.dmdl.analyzer.driver.BasicTypeDriver;
import com.asakusafw.dmdl.analyzer.driver.CollectionTypeDriver;
import com.asakusafw.dmdl.model.AstModelDefinition;
import com.asakusafw.dmdl.model.AstScript;
import com.asakusafw.dmdl.model.BasicTypeKind;
import com.asakusafw.dmdl.parser.DmdlParser;
import com.asakusafw.dmdl.parser.DmdlSyntaxException;
import com.asakusafw.dmdl.semantics.DmdlSemantics;
import com.asakusafw.dmdl.semantics.ModelSymbol;
import com.asakusafw.dmdl.semantics.PropertySymbol;
import com.asakusafw.dmdl.semantics.Type;
import com.asakusafw.dmdl.semantics.type.BasicType;
import com.asakusafw.dmdl.spi.AttributeDriver;
import com.asakusafw.dmdl.spi.TypeDriver;
/**
* Testing utilities for this project.
*/
public abstract class DmdlTesterRoot {
private final Logger log = LoggerFactory.getLogger(getClass());
/**
* a test name handler.
*/
@Rule
public TestName currentTestName = new TestName();
/**
* {@link TypeDriver}s.
*/
protected final List<TypeDriver> typeDrivers = Stream.of(new BasicTypeDriver(), new CollectionTypeDriver())
.collect(Collectors.toList());
/**
* {@link AttributeDriver}s.
*/
protected final List<AttributeDriver> attributeDrivers = new ArrayList<>();
/**
* Returns a type matcher.
* @param kind type kind
* @return the matcher
*/
protected Matcher<Type> type(BasicTypeKind kind) {
return new BaseMatcher<Type>() {
@Override
public boolean matches(Object object) {
if (object instanceof BasicType) {
return ((BasicType) object).getKind() == kind;
}
return false;
}
@Override
public void describeTo(Description desc) {
desc.appendText(kind.name());
}
};
}
/**
* Returns a model matcher.
* @param name the model name
* @return the matcher
*/
protected Matcher<ModelSymbol> model(String name) {
return new BaseMatcher<ModelSymbol>() {
@Override
public boolean matches(Object object) {
if (object instanceof ModelSymbol) {
return ((ModelSymbol) object).getName().identifier.equals(name);
}
return false;
}
@Override
public void describeTo(Description desc) {
desc.appendText(name);
}
};
}
/**
* Returns a property matcher.
* @param name the property name
* @return the matcher
*/
protected Matcher<PropertySymbol> property(String name) {
return new BaseMatcher<PropertySymbol>() {
@Override
public boolean matches(Object object) {
if (object instanceof PropertySymbol) {
return ((PropertySymbol) object).getName().identifier.equals(name);
}
return false;
}
@Override
public void describeTo(Description desc) {
desc.appendText(name);
}
};
}
/**
* Returns a property matcher.
* @param modelName the model name
* @param name the property name
* @return the matcher
*/
protected Matcher<PropertySymbol> property(String modelName, String name) {
return new BaseMatcher<PropertySymbol>() {
@Override
public boolean matches(Object object) {
if (object instanceof PropertySymbol) {
PropertySymbol property = (PropertySymbol) object;
return property.getName().identifier.equals(name)
&& property.getOwner().getName().identifier.equals(modelName);
}
return false;
}
@Override
public void describeTo(Description desc) {
desc.appendText(MessageFormat.format(
"{0}.{1}",
modelName,
name));
}
};
}
/**
* Returns a matcher which tests whether RHS is in LHS.
* @param <T> the target data type
* @param matcher RHS
* @return the matcher
*/
protected static <T> Matcher<Iterable<T>> has(Matcher<T> matcher) {
return new BaseMatcher<Iterable<T>>() {
@Override
public boolean matches(Object item) {
for (Object o : (Iterable<?>) item) {
if (matcher.matches(o)) {
return true;
}
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText("has ").appendDescriptionOf(matcher);
}
};
}
/**
* Resolves context script.
* @param lines explicit source lines
* @return the resolved
*/
protected DmdlSemantics resolve(String... lines) {
try {
return resolve0(lines);
} catch (DmdlSemanticException e) {
e.getDiagnostics().forEach(it -> log.error("{}", it));
throw new AssertionError(e.getDiagnostics());
}
}
/**
* Assert semantic error should occur.
* @param lines explicit source lines
* @return error object
*/
protected DmdlSemanticException shouldSemanticError(String... lines) {
try {
resolve0(lines);
throw new AssertionError("error should be raised");
} catch (DmdlSemanticException e) {
return e;
}
}
/**
* Resolves context script.
* @param lines explicit source lines
* @return the resolved
* @throws DmdlSemanticException if failed to resolve
*/
protected DmdlSemantics resolve0(String... lines) throws DmdlSemanticException {
AstScript script = parse(lines);
DmdlAnalyzer result = new DmdlAnalyzer(typeDrivers, attributeDrivers);
for (AstModelDefinition<?> model : script.models) {
result.addModel(model);
}
DmdlSemantics resolved = result.resolve();
return resolved;
}
/**
* Parses context script.
* @param lines explicit source lines
* @return the parsed
*/
protected AstScript parse(String... lines) {
try {
return parse0(lines);
}
catch (DmdlSyntaxException e) {
throw new AssertionError(e);
}
}
/**
* Assert syntax error should occur.
* @return error object
* @param lines explicit source lines
*/
protected DmdlSyntaxException shouldSyntaxError(String... lines) {
try {
parse0(lines);
throw new AssertionError("error should be raised");
} catch (DmdlSyntaxException e) {
return e;
}
}
/**
* Parses context script.
* @param lines explicit source lines
* @return the parsed
* @throws DmdlSyntaxException if failed to parse
*/
protected AstScript parse0(String... lines) throws DmdlSyntaxException {
return lines.length == 0 ? parseFile(currentTestName.getMethodName()) : parseLines(lines);
}
private AstScript parseFile(String resource) throws DmdlSyntaxException {
try {
String fileName = resource + ".txt";
URL url = getClass().getResource(fileName);
assertThat(fileName, url, is(not(nullValue())));
URI uri;
try {
uri = url.toURI();
} catch (URISyntaxException e) {
uri = null;
}
try (Reader r = new InputStreamReader(url.openStream(), "UTF-8");) {
DmdlParser parser = new DmdlParser();
AstScript script = parser.parse(r, uri);
return script;
}
} catch (IOException e) {
throw new AssertionError(e);
}
}
private static AstScript parseLines(String... lines) throws DmdlSyntaxException {
try {
try (Reader r = new StringReader(Arrays.stream(lines)
.map(s -> s.replace('\'', '"'))
.map(s -> s.replace('`', '"'))
.collect(Collectors.joining(System.lineSeparator())))) {
DmdlParser parser = new DmdlParser();
AstScript script = parser.parse(r, URI.create("testing"));
return script;
}
} catch (IOException e) {
throw new AssertionError(e);
}
}
}