blob: ca69888db674fb7a3d501736ede8399e301dc6b5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.uima.fit.validation;
import static java.util.Arrays.stream;
import static java.util.ServiceLoader.load;
import static java.util.stream.StreamSupport.stream;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.uima.cas.CAS;
import org.apache.uima.cas.CASException;
import org.apache.uima.jcas.JCas;
/**
* Validate a (J)CAS.
*/
public class Validator {
private Collection<ValidationCheck> checks;
public Validator(Collection<ValidationCheck> checks) {
this.checks = checks;
}
public ValidationSummary check(JCas aJCas) throws ValidationException {
ValidationSummary summary = new ValidationSummary();
for (ValidationCheck check : checks) {
try {
if (check instanceof CasValidationCheck) {
summary.addAll(((CasValidationCheck) check).validate(aJCas.getCas()));
} else if (check instanceof JCasValidationCheck) {
summary.addAll(((JCasValidationCheck) check).validate(aJCas));
} else {
throw new IllegalArgumentException(
"Unknown ValidationCheck type: [" + check.getClass().getName() + "]");
}
} catch (ValidationCheckSkippedException e) {
summary.add(ValidationResult.info(check, "Skipped: %s", e.getMessage()));
} catch (ValidationCheckException e) {
summary.add(ValidationResult.error(check, "%s", e.getMessage()));
}
}
return summary;
}
public ValidationSummary check(CAS cas) throws ValidationException {
ValidationSummary summary = new ValidationSummary();
for (ValidationCheck check : checks) {
try {
if (check instanceof CasValidationCheck) {
summary.addAll(((CasValidationCheck) check).validate(cas));
} else if (check instanceof JCasValidationCheck) {
try {
summary.addAll(((JCasValidationCheck) check).validate(cas.getJCas()));
} catch (CASException e) {
throw new ValidationException(e);
}
} else {
throw new IllegalArgumentException(
"Unknown ValidationCheck type: [" + check.getClass().getName() + "]");
}
} catch (ValidationCheckSkippedException e) {
summary.add(ValidationResult.info(check, "Skipped: %s", e.getMessage()));
} catch (ValidationCheckException e) {
summary.add(ValidationResult.error(check, "%s", e.getMessage()));
}
}
return summary;
}
public Collection<ValidationCheck> getChecks() {
return checks;
}
public static class Builder {
private final Set<ValidationCheck> checks = new LinkedHashSet<>();
private final Set<Pattern> includePatterns = new HashSet<>();
private final Set<Class<?>> includeTypes = new HashSet<>();
private final Set<Pattern> excludePatterns = new HashSet<>();
private final Set<Class<?>> excludeTypes = new HashSet<>();
private boolean skipAutoDetection = false;
/**
* Add the given check instance to the validator. This allows even adding checks which are not
* available via the Java Service Locator, which take parameters or which are otherwise stateful
* (assuming that the resulting validator is not shared between threads).
* <p>
* <b>Note:</b> Includes/excludes do also apply do checks added via this method.
*
* @param check
* a check instance to use.
*/
public Validator.Builder withCheck(CasValidationCheck check) {
checks.add(check);
return this;
}
/**
* Disable auto-detection of checks.
*/
public Validator.Builder withoutAutoDetectedChecks() {
skipAutoDetection = true;
return this;
}
/**
* Enable auto-detection of checks (the default behavior).
*/
public Validator.Builder withAutoDetectedChecks() {
skipAutoDetection = false;
return this;
}
/**
* Skip any checks with the given names. Subtypes of the given classes are not skipped.
* <p>
* <b>Note:</b> Excludes are applied after includes.
*
* @param className
* names of check classes to be excluded.
*/
public Validator.Builder excludingByName(String... className) {
stream(className)
.map(Pattern::quote)
.map(Pattern::compile)
.forEach(excludePatterns::add);
return this;
}
/**
* Skip any checks with names matching the given regular expressions.
* <p>
* <b>Note:</b> Excludes are applied after includes.
*
* @param patterns
* regular expressions matching check class names to be excluded.
*/
public Validator.Builder excludingByPattern(String... patterns) {
stream(patterns)
.map(Pattern::compile)
.forEach(excludePatterns::add);
return this;
}
/**
* Skips any checks of the given types (includes checks that are subclasses or implementations
* of the given types).
* <p>
* <b>Note:</b> Excludes are applied after includes.
*
* @param types
* check type names to be excluded.
*/
public Validator.Builder excludingByType(Class<?>... types) {
stream(types).forEach(excludeTypes::add);
return this;
}
/**
* Retain only checks with the given names. Subtypes of the given classes are not retained.
* <p>
* <b>Note:</b> Excludes are applied after includes.
*
* @param className
* names of check classes to be included.
*/
public Validator.Builder includingByName(String... className) {
stream(className)
.map(Pattern::quote)
.map(Pattern::compile)
.forEach(includePatterns::add);
return this;
}
/**
* Retain any checks with names matching the given regular expressions.
* <p>
* <b>Note:</b> Excludes are applied after includes.
*
* @param patterns
* regular expressions matching check class names to be included.
*/
public Validator.Builder includingByPattern(String... patterns) {
stream(patterns)
.map(Pattern::compile)
.forEach(includePatterns::add);
return this;
}
/**
* Retain any checks of the given types (includes checks that are subclasses or implementations
* of the given types).
* <p>
* <b>Note:</b> Excludes are applied after includes.
*
* @param types
* check type names to be included.
*/
public Validator.Builder includingByType(Class<?>... types) {
stream(types).forEach(includeTypes::add);
return this;
}
private Validator.Builder autoDetectChecks() {
stream(load(ValidationCheck.class).spliterator(), false)
.forEachOrdered(checks::add);
return this;
}
public Validator build() {
if (!skipAutoDetection) {
autoDetectChecks();
}
if (!includePatterns.isEmpty()) {
checks.removeIf(check -> includePatterns.stream()
.noneMatch(p -> p.matcher(check.getClass().getName()).matches()));
}
if (!includeTypes.isEmpty()) {
checks.removeIf(check -> includeTypes.stream()
.noneMatch(t -> t.isAssignableFrom(check.getClass())));
}
if (!excludePatterns.isEmpty()) {
checks.removeIf(check -> excludePatterns.stream()
.anyMatch(p -> p.matcher(check.getClass().getName()).matches()));
}
if (!excludeTypes.isEmpty()) {
checks.removeIf(check -> excludeTypes.stream()
.anyMatch(t -> t.isAssignableFrom(check.getClass())));
}
return new Validator(checks);
}
}
}