/*
* Copyright 2013-2016 Skynav, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY SKYNAV, INC. AND ITS CONTRIBUTORS “AS IS” AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL SKYNAV, INC. OR ITS CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.skynav.ttv.app;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Set;
import java.util.Stack;
import javax.xml.bind.Binder;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.UnmarshalException;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLFilterImpl;
import com.skynav.ttv.model.Model;
import com.skynav.ttv.model.Models;
import com.skynav.ttv.model.value.Length;
import com.skynav.ttv.model.value.Time;
import com.skynav.ttv.model.value.TimeParameters;
import com.skynav.ttv.util.Annotations;
import com.skynav.ttv.util.Configuration;
import com.skynav.ttv.util.ConfigurationDefaults;
import com.skynav.ttv.util.ExternalParameters;
import com.skynav.ttv.util.IOUtil;
import com.skynav.ttv.util.Location;
import com.skynav.ttv.util.Locators;
import com.skynav.ttv.util.Message;
import com.skynav.ttv.util.Reporter;
import com.skynav.ttv.util.Reporters;
import com.skynav.ttv.verifier.VerifierContext;
import com.skynav.ttv.verifier.util.Lengths;
import com.skynav.ttv.verifier.util.MixedUnitsTreatment;
import com.skynav.ttv.verifier.util.NegativeTreatment;
import com.skynav.ttv.verifier.util.Timing;
import com.skynav.xml.helpers.Documents;
import com.skynav.xml.helpers.Sniffer;
import com.skynav.xml.helpers.XML;
import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;
public class TimedTextVerifier implements VerifierContext {
public static final int RV_PASS = 0;
public static final int RV_FAIL = 1;
public static final int RV_USAGE = 2;
public static final int RV_RESTART = 3;
public static final int RV_FLAG_ERROR_UNEXPECTED = 0x000001;
public static final int RV_FLAG_ERROR_EXPECTED_MATCH = 0x000002;
public static final int RV_FLAG_ERROR_EXPECTED_MISMATCH = 0x000004;
public static final int RV_FLAG_WARNING_UNEXPECTED = 0x000010;
public static final int RV_FLAG_WARNING_EXPECTED_MATCH = 0x000020;
public static final int RV_FLAG_WARNING_EXPECTED_MISMATCH = 0x000040;
public static final String DEFAULT_ENCODING = "UTF-8";
// miscelaneous defaults
private static final String defaultReporterFileEncoding = Reporters.getDefaultEncoding();
// banner text
private static final String title = "Timed Text Verifier (TTV) [" + Version.CURRENT + "]";
private static final String copyright = "Copyright 2013-16 Skynav, Inc.";
private static final String banner = title + " " + copyright;
// usage text
private static final String repositoryURL =
"https://github.com/skynav/ttt";
private static final String repositoryInfo =
"Source Repository: " + repositoryURL;
// option and usage info
private static final String[][] shortOptionSpecifications = new String[][] {
{ "d", "see --debug" },
{ "q", "see --quiet" },
{ "v", "see --verbose" },
{ "?", "see --help" },
};
private static final Collection<OptionSpecification> shortOptions;
static {
Set<OptionSpecification> s = new java.util.TreeSet<OptionSpecification>();
for (String[] spec : shortOptionSpecifications) {
s.add(new OptionSpecification(spec[0], spec[1]));
}
shortOptions = Collections.unmodifiableSet(s);
}
private static final String[][] longOptionSpecifications = new String[][] {
{ "config", "FILE", "specify path to configuration file" },
{ "debug", "", "enable debug output (may be specified multiple times to increase debug level)" },
{ "debug-exceptions", "", "enable stack traces on exceptions (implies --debug)" },
{ "debug-level", "LEVEL", "enable debug output at specified level (default: 0)" },
{ "disable-warnings", "", "disable warnings (both hide and don't count warnings)" },
{ "expect-errors", "COUNT", "expect count errors or -1 meaning unspecified expectation (default: -1)" },
{ "expect-warnings", "COUNT", "expect count warnings or -1 meaning unspecified expectation (default: -1)" },
{ "extension-schema", "NS URL", "add schema for namespace NS at location URL to grammar pool (may be specified multiple times)" },
{ "external-duration", "DURATION", "specify root temporal extent duration for document processing context" },
{ "external-extent", "EXTENT", "specify root container region extent for document processing context" },
{ "external-frame-rate", "RATE", "specify frame rate for document processing context" },
{ "force-encoding", "NAME", "force use of named character encoding, overriding default and resource specified encoding" },
{ "force-model", "NAME", "force use of named model, overriding default model and resource specified model" },
{ "help", "", "show usage help" },
{ "hide-warnings", "", "hide warnings (but count them)" },
{ "hide-resource-location", "", "hide resource location (default: show)" },
{ "hide-resource-path", "", "hide resource path (default: show)" },
{ "model", "NAME", "specify model name (default: " + Models.getDefaultModelName() + ")" },
{ "no-warn-on", "TOKEN", "disable warning specified by warning TOKEN, where multiple instances of this option may be specified" },
{ "no-verbose", "", "disable verbose output (resets verbosity level to 0)" },
{ "quiet", "", "don't show banner" },
{ "reporter", "REPORTER", "specify reporter, where REPORTER is " + Reporters.getReporterNamesJoined() + " (default: " +
Reporters.getDefaultReporterName()+ ")" },
{ "reporter-file", "FILE", "specify path to file to which reporter output is to be written" },
{ "reporter-file-encoding", "ENCODING", "specify character encoding of reporter output (default: utf-8)" },
{ "reporter-file-append", "", "if reporter file already exists, then append output to it" },
{ "reporter-include-source", "", "include source context in report messages" },
{ "servlet", "", "configure defaults for servlet operation" },
{ "show-models", "", "show built-in verification models (use with --verbose to show more details)" },
{ "show-repository", "", "show source code repository information" },
{ "show-resource-location", "", "show resource location (default: show)" },
{ "show-resource-path", "", "show resource path (default: show)" },
{ "show-validator", "", "show platform validator information" },
{ "show-warning-tokens", "", "show warning tokens (use with --verbose to show more details)" },
{ "verbose", "", "enable verbose output (may be specified multiple times to increase verbosity level)" },
{ "verbos-level", "LEVEL", "enable verbose output at specified level (default: 0)" },
{ "treat-foreign-as", "TOKEN", "specify treatment for foreign namespace vocabulary, where TOKEN is error|warning|info|allow (default: " +
ForeignTreatment.getDefault().name().toLowerCase() + ")" },
{ "treat-warning-as-error", "", "treat warning as error (overrides --disable-warnings)" },
{ "until-phase", "PHASE", "verify up to specified phase, where PHASE is none|resource|wellformedness|validity|semantics|all (default: " +
Phase.getDefault().name().toLowerCase() + ")" },
{ "warn-on", "TOKEN", "enable warning specified by warning TOKEN, where multiple instances of this option may be specified" },
};
private static final Collection<OptionSpecification> longOptions;
static {
Set<OptionSpecification> s = new java.util.TreeSet<OptionSpecification>();
for (String[] spec : longOptionSpecifications) {
s.add(new OptionSpecification(spec[0], spec[1], spec[2]));
}
longOptions = Collections.unmodifiableSet(s);
}
private static final String usageCommand =
"java -jar ttv.jar [options] URL*";
private static final String[][] nonOptions = new String[][] {
{ "URL", "an absolute or relative URL; if relative, resolved against current working directory" },
};
// default warnings
private static final Object[][] defaultWarningSpecifications = new Object[][] {
{ "all", Boolean.FALSE, "all warnings" },
{ "duplicate-idref-in-agent", Boolean.FALSE, "duplicate IDREF in 'ttm:agent' attribute"},
{ "duplicate-idref-in-style", Boolean.FALSE, "duplicate IDREF in 'style' attribute"},
{ "duplicate-idref-in-style-no-intervening", Boolean.TRUE, "duplicate IDREF in 'style' attribute without intervening IDREF" },
{ "duplicate-role", Boolean.TRUE, "duplicate role token in 'ttm:role' attribute"},
{ "foreign", Boolean.FALSE, "attribute or element in non-TTML (foreign) namespace"},
{ "ignored-profile-attribute", Boolean.TRUE, "'ttp:profile' attribute ignored when 'ttp:profile' element is present"},
{ "missing-agent-actor", Boolean.TRUE, "no 'ttm:agent' child present in 'ttm:agent' element of type 'character'"},
{ "missing-agent-name", Boolean.TRUE, "no 'ttm:name' child present in 'ttm:agent' element"},
{ "missing-profile", Boolean.FALSE, "neither 'ttp:profile' attribute nor 'ttp:profile' element is present"},
{ "missing-type-for-external-source", Boolean.TRUE, "no 'type' attribute present with external resource source reference"},
{ "negative-origin", Boolean.FALSE, "either coordinate in 'tts:origin' is negative"},
{ "out-of-range-opacity", Boolean.TRUE, "'tts:opacity' is out of range [0,1]"},
{ "quoted-generic-font-family", Boolean.FALSE, "generic font family appears in quoted form, negating generic name function" },
{ "references-extension-role", Boolean.FALSE, "'ttp:role' attribute specifies extension role"},
{ "references-external-image", Boolean.FALSE, "'smpte:backgroundImage' referernces external image"},
{ "references-non-standard-extension", Boolean.FALSE, "'ttp:extension' element references non-standard extension"},
{ "references-non-standard-profile", Boolean.FALSE, "'ttp:profile' attribute or element references non-standard profile"},
{ "references-other-extension-namespace", Boolean.FALSE, "'ttp:extensions' element references other extension namespace"},
{ "xsi-schema-location", Boolean.FALSE, "'xsi:schemaLocation' attribute used"},
{ "xsi-no-namespace-schema-location", Boolean.TRUE, "'xsi:noNamespaceSchemaLocation' attribute used"},
{ "xsi-other-attribute", Boolean.FALSE, "'xsi:nil' or 'xsi:type' attribute used"},
};
// encodings
private static final Charset defaultEncoding;
private static final Charset defaultOutputEncoding;
private static final Charset asciiEncoding;
static {
Charset de, ae;
try {
de = Charset.forName(DEFAULT_ENCODING);
ae = Charset.forName("US-ASCII");
} catch (RuntimeException e) {
de = Charset.defaultCharset();
ae = null;
}
defaultEncoding = de;
defaultOutputEncoding = defaultEncoding;
asciiEncoding = ae;
}
private static final List<Charset> permittedEncodings;
static {
List<Charset> l = new java.util.ArrayList<Charset>();
try {
l.add(Charset.forName("US-ASCII"));
l.add(Charset.forName("ISO-8859-1"));
l.add(Charset.forName("UTF-8"));
l.add(Charset.forName("UTF-16LE"));
l.add(Charset.forName("UTF-16BE"));
l.add(Charset.forName("UTF-16"));
l.add(Charset.forName("UTF-32LE"));
l.add(Charset.forName("UTF-32BE"));
l.add(Charset.forName("UTF-32"));
} catch (RuntimeException e) {
}
permittedEncodings = Collections.unmodifiableList(l);
}
// miscellaneous statics
private static final QName emptyName = new QName("", "");
// option state
private String expectedErrors;
private String expectedWarnings;
private String externalDuration;
private String externalExtent;
private String externalFrameRate;
private Map<String,String> extensionSchemas = new java.util.HashMap<String,String>();
private String forceEncodingName;
private String forceModelName;
private boolean includeSource;
private String modelName;
private boolean quiet;
private boolean showModels;
private boolean showRepository;
private boolean showValidator;
private boolean showWarningTokens;
private String treatForeignAs;
private String untilPhase;
// derived option state
private Configuration configuration;
private Charset forceEncoding;
private Model forceModel;
private Model model;
private ForeignTreatment foreignTreatment;
private Phase lastPhase;
private double parsedExternalFrameRate;
private double parsedExternalDuration;
private double[] parsedExternalExtent;
// global processing state
private boolean restarted;
private PrintWriter showOutput;
private ExternalParametersStore externalParameters = new ExternalParametersStore();
private Reporter reporter;
private SchemaFactory schemaFactory;
private boolean nonPoolGrammarSupported;
private Map<List<URL>,Schema> schemas = new java.util.HashMap<List<URL>,Schema>();
private Map<String,Results> results = new java.util.HashMap<String,Results>();
// per-resource processing state
private Phase currentPhase;
private Model resourceModel;
private String resourceUriString;
private Map<String,Object> resourceState;
private URI resourceUri;
private Charset resourceEncoding;
private ByteBuffer resourceBufferRaw;
private int resourceExpectedErrors = -1;
private int resourceExpectedWarnings = -1;
private Binder<Node> binder;
private Object rootBinding;
private QName rootName;
private enum ForeignTreatment {
Error, // error, don't apply foreign validation
Warning, // warning, but apply foreign validation
Info, // info only, but apply foreign validation
Allow; // no logging, but apply foreign validation
public static ForeignTreatment valueOfIgnoringCase(String value) {
if (value == null)
throw new IllegalArgumentException();
for (ForeignTreatment v: values()) {
if (value.equalsIgnoreCase(v.name()))
return v;
}
throw new IllegalArgumentException();
}
public static ForeignTreatment getDefault() {
return Warning;
}
}
private enum Phase {
// N.B. Do not change the following order, since ordinal() is used below.
None,
Resource,
WellFormedness,
Validity,
Semantics,
Restarted,
All;
public boolean isEnabled(Phase phase) {
return phase.ordinal() <= ordinal();
}
public static Phase valueOfIgnoringCase(String value) {
if (value == null)
throw new IllegalArgumentException();
for (Phase v: values()) {
if (value.equalsIgnoreCase(v.name()))
return v;
}
throw new IllegalArgumentException();
}
public static Phase getDefault() {
return All;
}
}
public TimedTextVerifier() {
this(null, null, null, false, null);
}
public TimedTextVerifier(Reporter reporter, PrintWriter reporterOutput, String reporterOutputEncoding, boolean reporterIncludeSource, PrintWriter showOutput) {
if (reporter == null)
reporter = Reporters.getDefaultReporter();
setReporter(reporter, reporterOutput, reporterOutputEncoding, reporterIncludeSource);
setShowOutput(showOutput);
}
private void resetOptionsState(boolean restart) {
expectedErrors = null;
expectedWarnings = null;
externalDuration = null;
externalExtent = null;
externalFrameRate = null;
extensionSchemas = new java.util.HashMap<String,String>();
forceEncodingName = null;
forceModelName = null;
includeSource = false;
modelName = null;
quiet = false;
showModels = false;
showRepository = false;
showValidator = false;
showWarningTokens = false;
treatForeignAs = null;
untilPhase = null;
}
private void resetDerivedOptionsState(boolean restart) {
configuration = null;
forceEncoding = null;
forceModel = null;
// model = null;
foreignTreatment = null;
lastPhase = restart ? Phase.Restarted : Phase.None;
parsedExternalFrameRate = 0;
parsedExternalDuration = 0;
parsedExternalExtent = null;
}
private void resetGlobalProcessingState(Reporter reporter, PrintWriter showOutput, boolean restart) {
if (restart) {
reporter.resetAllState(restart);
this.reporter = reporter;
this.showOutput = showOutput;
} else {
this.reporter = Reporters.getDefaultReporter();
this.showOutput = null;
}
externalParameters = new ExternalParametersStore();
schemaFactory = null;
nonPoolGrammarSupported = false;
schemas = new java.util.HashMap<List<URL>,Schema>();
results = new java.util.HashMap<String,Results>();
}
private void resetResourceState() {
resetResourceState(false);
}
private void resetResourceState(boolean restart) {
currentPhase = null;
resourceModel = model;
resourceUriString = null;
resourceState = new java.util.HashMap<String,Object>();
resourceUri = null;
resourceEncoding = null;
resourceBufferRaw = null;
resourceExpectedErrors = -1;
resourceExpectedWarnings = -1;
binder = null;
rootBinding = null;
rootName = null;
Reporter reporter = getReporter();
if (reporter != null) {
reporter.resetResourceState(restart);
if (resourceModel != null)
resourceModel.configureReporter(reporter);
}
}
private void resetAllState(Reporter reporter, PrintWriter showOutput, OptionProcessor optionProcessor, boolean restart) {
if (optionProcessor != null)
optionProcessor.resetAllState(restart);
resetResourceState(restart);
resetDerivedOptionsState(restart);
resetOptionsState(restart);
resetGlobalProcessingState(reporter, showOutput, restart);
}
private void resetReporter() {
reporter.flush();
setReporter(Reporters.getDefaultReporter(), null, null, false, true);
}
private void setReporter(Reporter reporter, PrintWriter reporterOutput, String reporterOutputEncoding, boolean reporterIncludeSource) {
setReporter(reporter, reporterOutput, reporterOutputEncoding, reporterIncludeSource, false);
}
public void setReporter(Reporter reporter, PrintWriter reporterOutput, String reporterOutputEncoding, boolean reporterIncludeSource, boolean closeOldReporter) {
if (reporter == this.reporter)
return;
if (this.reporter != null)
this.reporter.flush();
if (closeOldReporter) {
try {
if (this.reporter != null)
this.reporter.close();
} catch (IOException e) {
} finally {
this.reporter = null;
}
}
try {
reporter.open(defaultWarningSpecifications, reporterOutput, null, reporterOutputEncoding, reporterIncludeSource);
this.reporter = reporter;
this.includeSource = reporterIncludeSource;
} catch (Throwable e) {
this.reporter = null;
}
}
private void setReporter(String reporterName, String reporterFileName, String reporterFileEncoding, boolean reporterFileAppend, boolean reporterIncludeSource) {
assert reporterName != null;
Reporter reporter = Reporters.getReporter(reporterName);
if (reporter == null)
throw new InvalidOptionUsageException("reporter", "unknown reporter: " + reporterName);
if (reporterFileName != null) {
if (reporterFileEncoding == null)
reporterFileEncoding = defaultReporterFileEncoding;
try {
Charset.forName(reporterFileEncoding);
} catch (IllegalCharsetNameException e) {
throw new InvalidOptionUsageException("reporter-file-encoding", "illegal encoding name: " + reporterFileEncoding);
} catch (UnsupportedCharsetException e) {
throw new InvalidOptionUsageException("reporter-file-encoding", "unsupported encoding: " + reporterFileEncoding);
}
}
File reporterFile = null;
boolean createdReporterFile = false;
PrintWriter reporterOutput = null;
if (reporterFileName != null) {
reporterFile = new File(reporterFileName);
FileOutputStream os = null;
try {
createdReporterFile = reporterFile.createNewFile();
os = new FileOutputStream(reporterFile, reporterFileAppend);
reporterOutput = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os, reporterFileEncoding)));
} catch (Throwable e) {
IOUtil.closeSafely(os);
if (createdReporterFile)
IOUtil.deleteSafely(reporterFile);
}
}
setReporter(reporter, reporterOutput, reporterFileEncoding, reporterIncludeSource);
if (reporterOutput != null) {
if (getReporter().getOutput() != reporterOutput) {
reporterOutput.close();
if (createdReporterFile)
IOUtil.deleteSafely(reporterFile);
}
}
}
@Override
public ExternalParameters getExternalParameters() {
return externalParameters;
}
@Override
public Reporter getReporter() {
return reporter;
}
@Override
public Model getModel() {
if (this.forceModel != null)
return this.forceModel;
else if (this.resourceModel != null)
return this.resourceModel;
else if (this.model != null)
return this.model;
else
return Models.getDefaultModel();
}
public Charset getEncoding() {
if (this.forceEncoding != null)
return this.forceEncoding;
else if (this.resourceEncoding != null)
return this.resourceEncoding;
else
return defaultEncoding;
}
@Override
public QName getBindingElementName(Object value) {
Node xmlNode = binder.getXMLNode(value);
if (xmlNode != null) {
Object jaxbBinding = binder.getJAXBNode(xmlNode);
if (jaxbBinding instanceof JAXBElement<?>) {
JAXBElement<?> jaxbNode = (JAXBElement<?>) jaxbBinding;
return jaxbNode.getName();
} else
return new QName(xmlNode.getNamespaceURI(), xmlNode.getLocalName());
}
return emptyName;
}
@Override
public Object getBindingElementParent(Object value) {
Node node = getXMLNode(value);
if (node == null)
return null;
else {
Node parentNode = node.getParentNode();
Object parent = getBindingElement(parentNode);
if (parent != null)
return parent;
else if (rootBinding != null) {
return getModel().getSemanticsVerifier().findBindingElement(rootBinding, parentNode);
} else
return null;
}
}
@Override
public Object getBindingElement(Node node) {
return binder.getJAXBNode(node);
}
@Override
public Node getXMLNode(Object value) {
return binder.getXMLNode(value);
}
@Override
public void setResourceState(String key, Object value) {
if (resourceState != null)
resourceState.put(key, value);
}
@Override
public Object getResourceState(String key) {
if (resourceState != null)
return resourceState.get(key);
else
return null;
}
@Override
public Object extractResourceState(String key) {
Object state = getResourceState(key);
setResourceState(key, null);
return state;
}
private List<String> preProcessOptions(List<String> args, OptionProcessor optionProcessor) {
args = processReporterOptions(args, optionProcessor);
args = processConfigurationOptions(args, optionProcessor);
if (optionProcessor != null)
args = optionProcessor.preProcessOptions(args, configuration, shortOptions, longOptions);
return args;
}
private List<String> processReporterOptions(List<String> args, OptionProcessor optionProcessor) {
String reporterName = null;
String reporterFileName = null;
String reporterFileEncoding = null;
boolean reporterFileAppend = false;
boolean reporterIncludeSource = false;
List<String> skippedArgs = new java.util.ArrayList<String>();
for (int i = 0, n = args.size(); i < n; ++i) {
String arg = args.get(i);
if (arg.indexOf("--") == 0) {
String option = arg.substring(2);
if (option.equals("reporter")) {
if (i + 1 >= n)
throw new MissingOptionArgumentException("--" + option);
reporterName = args.get(++i);
} else if (option.equals("reporter-file")) {
if (i + 1 >= n)
throw new MissingOptionArgumentException("--" + option);
reporterFileName = args.get(++i);
} else if (option.equals("reporter-file-encoding")) {
if (i + 1 >= n)
throw new MissingOptionArgumentException("--" + option);
reporterFileEncoding = args.get(++i);
} else if (option.equals("reporter-file-append")) {
reporterFileAppend = true;
} else if (option.equals("reporter-include-source")) {
reporterIncludeSource = true;
} else {
skippedArgs.add(arg);
}
} else
skippedArgs.add(arg);
}
if (reporterName != null)
setReporter(reporterName, reporterFileName, reporterFileEncoding, reporterFileAppend, reporterIncludeSource);
return skippedArgs;
}
private List<String> processConfigurationOptions(List<String> args, OptionProcessor optionProcessor) {
String configFilePath = null;
List<String> skippedArgs = new java.util.ArrayList<String>();
for (int i = 0, n = args.size(); i < n; ++i) {
String arg = args.get(i);
if (arg.indexOf("--") == 0) {
String option = arg.substring(2);
if (option.equals("config")) {
if (i + 1 >= n)
throw new MissingOptionArgumentException("--" + option);
configFilePath = args.get(++i);
} else {
skippedArgs.add(arg);
}
} else
skippedArgs.add(arg);
}
configuration = loadConfiguration(configFilePath, optionProcessor);
return skippedArgs;
}
private Configuration loadConfiguration(String configFilePath, OptionProcessor optionProcessor) {
try {
URL locator;
if (configFilePath != null) {
File f = new File(configFilePath);
if (!f.isAbsolute())
f = new File(new File(".").getCanonicalFile(), configFilePath);
locator = f.toURI().toURL();
} else
locator = null;
return loadConfiguration(locator, optionProcessor);
} catch (IOException e) {
getReporter().logError(e);
return null;
}
}
private Configuration loadConfiguration(URL locator, OptionProcessor optionProcessor) {
Reporter reporter = getReporter();
if ((locator == null) && (optionProcessor != null))
locator = optionProcessor.getDefaultConfigurationLocator();
if (locator == null)
locator = Configuration.getDefaultConfigurationLocator(TimedTextVerifier.class, null);
try {
ConfigurationDefaults configDefaults = (optionProcessor != null) ? optionProcessor.getConfigurationDefaults(locator) : null;
if (configDefaults == null)
configDefaults = new ConfigurationDefaults(locator);
Class<? extends Configuration> configClass = (optionProcessor != null) ? optionProcessor.getConfigurationClass() : null;
if (configClass == null)
configClass = Configuration.class;
return Configuration.fromLocator(locator, configDefaults, configClass, reporter);
} catch (IOException e) {
reporter.logError(e);
return null;
}
}
private List<String> parseArgs(List<String> args, OptionProcessor optionProcessor) {
args = processConfigurationArguments(args, optionProcessor);
args = processOptionArguments(args, optionProcessor);
args = processNonOptionArguments(args, optionProcessor);
processDerivedOptions(optionProcessor);
return args;
}
private List<String> processConfigurationArguments(List<String> args, OptionProcessor optionProcessor) {
if (configuration != null) {
for (Map.Entry<String,String> e : configuration.getOptions().entrySet()) {
String n = e.getKey();
String v = e.getValue();
List<String> option = new java.util.ArrayList<String>(2);
option.add("--" + n);
option.add(v);
int i = parseLongOption(option, 0, optionProcessor);
assert i > 0;
}
}
return args;
}
private List<String> processOptionArguments(List<String> args, OptionProcessor optionProcessor) {
int nonOptionIndex = -1;
for (int i = 0; i < args.size();) {
String arg = args.get(i);
if (arg.charAt(0) == '-') {
if (arg.charAt(1) != '-') {
if (arg.length() != 2)
throw new UnknownOptionException(arg);
i = parseShortOption(args, i, optionProcessor);
} else {
i = parseLongOption(args, i, optionProcessor);
}
} else {
nonOptionIndex = i;
break;
}
}
List<String> nonOptionArgs = new java.util.ArrayList<String>();
if (nonOptionIndex >= 0) {
for (int i = nonOptionIndex, n = args.size(); i < n; ++i)
nonOptionArgs.add(args.get(i));
}
return nonOptionArgs;
}
private int parseShortOption(List<String> args, int index, OptionProcessor optionProcessor) {
Reporter reporter = getReporter();
String option = args.get(index);
assert option.length() == 2;
option = option.substring(1);
switch (option.charAt(0)) {
case 'd':
reporter.incrementDebugLevel();
break;
case 'q':
quiet = true;
break;
case 'v':
reporter.incrementVerbosityLevel();
break;
case '?':
throw new ShowUsageException();
default:
if ((optionProcessor != null) && optionProcessor.hasOption(args.get(index)))
return optionProcessor.parseOption(args, index);
else
throw new UnknownOptionException("-" + option);
}
return index + 1;
}
private int parseLongOption(List<String> args, int index, OptionProcessor optionProcessor) {
Reporter reporter = getReporter();
String arg = args.get(index);
int numArgs = args.size();
String option = arg;
assert option.length() > 2;
option = option.substring(2);
if (option.equals("debug")) {
int debug = reporter.getDebugLevel();
if (debug < 1)
debug = 1;
else
debug += 1;
reporter.setDebugLevel(debug);
} else if (option.equals("debug-exceptions")) {
int debug = reporter.getDebugLevel();
if (debug < 2)
debug = 2;
reporter.setDebugLevel(debug);
} else if (option.equals("debug-level")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
String level = args.get(++index);
int debugNew;
try {
debugNew = Integer.parseInt(level);
} catch (NumberFormatException e) {
throw new InvalidOptionUsageException("debug-level", "bad syntax: " + level);
}
int debug = reporter.getDebugLevel();
if (debugNew > debug)
reporter.setDebugLevel(debugNew);
} else if (option.equals("disable-warnings")) {
reporter.disableWarnings();
} else if (option.equals("expect-errors")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
expectedErrors = args.get(++index);
} else if (option.equals("expect-warnings")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
expectedWarnings = args.get(++index);
} else if (option.equals("extension-schema")) {
if (index + 2 > numArgs)
throw new MissingOptionArgumentException("--" + option);
String namespaceURI = args.get(++index);
String schemaResourceURL = args.get(++index);
extensionSchemas.put(namespaceURI, schemaResourceURL);
} else if (option.equals("external-duration")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
externalDuration = args.get(++index);
} else if (option.equals("external-extent")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
externalExtent = args.get(++index);
} else if (option.equals("external-frame-rate")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
externalFrameRate = args.get(++index);
} else if (option.equals("force-encoding")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
forceEncodingName = args.get(++index);
} else if (option.equals("force-model")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
forceModelName = args.get(++index);
} else if (option.equals("help")) {
throw new ShowUsageException();
} else if (option.equals("hide-resource-location")) {
reporter.hideLocation();
} else if (option.equals("hide-resource-path")) {
reporter.hidePath();
} else if (option.equals("hide-warnings")) {
reporter.hideWarnings();
} else if (option.equals("model")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
modelName = args.get(++index);
// unlike other options, we immediately perform derivation on the model option
processModelOption(modelName);
} else if (option.equals("no-warn-on")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
String token = args.get(++index);
if (!reporter.hasDefaultWarning(token))
throw new InvalidOptionUsageException("--" + option, "token '" + token + "' is not a recognized warning token");
reporter.disableWarning(token);
} else if (option.equals("no-verbose")) {
reporter.setVerbosityLevel(0);
} else if (option.equals("quiet")) {
quiet = true;
} else if (option.equals("servlet")) {
reporter.hideLocation();
} else if (option.equals("show-models")) {
showModels = true;
} else if (option.equals("show-repository")) {
showRepository = true;
} else if (option.equals("show-resource-location")) {
reporter.showLocation();
} else if (option.equals("show-resource-path")) {
reporter.showPath();
} else if (option.equals("show-validator")) {
showValidator = true;
} else if (option.equals("show-warning-tokens")) {
showWarningTokens = true;
} else if (option.equals("treat-foreign-as")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
treatForeignAs = args.get(++index);
} else if (option.equals("treat-warning-as-error")) {
reporter.setTreatWarningAsError(true);
} else if (option.equals("until-phase")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
untilPhase = args.get(++index);
} else if (option.equals("verbose")) {
reporter.incrementVerbosityLevel();
} else if (option.equals("verbose-level")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
String level = args.get(++index);
int verboseNew;
try {
verboseNew = Integer.parseInt(level);
} catch (NumberFormatException e) {
throw new InvalidOptionUsageException("verbose-level", "bad syntax: " + level);
}
int verbose = reporter.getVerbosityLevel();
if (verboseNew > verbose)
reporter.setVerbosityLevel(verboseNew);
} else if (option.equals("warn-on")) {
if (index + 1 > numArgs)
throw new MissingOptionArgumentException("--" + option);
String token = args.get(++index);
if (!reporter.hasDefaultWarning(token))
throw new InvalidOptionUsageException("--" + option, "token '" + token + "' is not a recognized warning token");
reporter.enableWarning(token);
} else if ((optionProcessor != null) && optionProcessor.hasOption(arg)) {
return optionProcessor.parseOption(args, index);
} else
throw new UnknownOptionException("--" + option);
return index + 1;
}
private List<String> processNonOptionArguments(List<String> args, OptionProcessor optionProcessor) {
if (optionProcessor != null)
args = optionProcessor.processNonOptionArguments(args);
return args;
}
private List<String> processRestartArguments(List<String> args, List<String> nonOptionArgs, OptionProcessor optionProcessor) {
if (optionProcessor != null)
args = optionProcessor.processRestartArguments(args, nonOptionArgs, getRestartOptions());
return args;
}
private void processModelOption(String modelName) {
Model model;
if ((modelName != null) && !modelName.equals("auto")) {
model = Models.getModel(modelName);
if (model == null)
throw new InvalidOptionUsageException("model", "unknown model: " + modelName);
} else
model = Models.getDefaultModel();
model.configureReporter(reporter);
this.model = model;
}
private void processDerivedOptions(OptionProcessor optionProcessor) {
Reporter reporter = getReporter();
Charset forceEncoding;
if (forceEncodingName != null) {
try {
forceEncoding = Charset.forName(forceEncodingName);
} catch (Exception e) {
forceEncoding = null;
}
if (forceEncoding == null)
throw new InvalidOptionUsageException("force-encoding", "unknown encoding: " + forceEncodingName);
} else
forceEncoding = null;
this.forceEncoding = forceEncoding;
Model forceModel;
if (forceModelName != null) {
forceModel = Models.getModel(forceModelName);
if (forceModel == null)
throw new InvalidOptionUsageException("force-model", "unknown model: " + forceModelName);
} else
forceModel = null;
this.forceModel = forceModel;
if (treatForeignAs != null) {
try {
foreignTreatment = ForeignTreatment.valueOfIgnoringCase(treatForeignAs);
} catch (IllegalArgumentException e) {
throw new InvalidOptionUsageException("treat-foreign-as", "unknown token: " + treatForeignAs);
}
} else
foreignTreatment = ForeignTreatment.getDefault();
if (foreignTreatment == ForeignTreatment.Warning)
reporter.enableWarning("foreign");
if (untilPhase != null) {
try {
lastPhase = Phase.valueOfIgnoringCase(untilPhase);
} catch (IllegalArgumentException e) {
throw new InvalidOptionUsageException("until-phase", "unknown token: " + untilPhase);
}
} else
lastPhase = Phase.getDefault();
if (externalFrameRate != null) {
try {
parsedExternalFrameRate = Double.parseDouble(externalFrameRate);
getExternalParameters().setParameter("externalFrameRate", Double.valueOf(parsedExternalFrameRate));
} catch (NumberFormatException e) {
throw new InvalidOptionUsageException("external-frame-rate", "invalid syntax, must be a double: " + externalFrameRate);
}
} else
parsedExternalFrameRate = 30.0;
if (externalDuration != null) {
Time[] duration = new Time[1];
TimeParameters timeParameters = new TimeParameters(parsedExternalFrameRate);
if (Timing.isDuration(externalDuration, null, this, timeParameters, duration)) {
if (duration[0].getType() != Time.Type.Offset)
throw new InvalidOptionUsageException("external-duration", "must use offset time syntax only: " + externalDuration);
parsedExternalDuration = duration[0].getTime(timeParameters);
getExternalParameters().setParameter("externalDuration", Double.valueOf(parsedExternalDuration));
} else
throw new InvalidOptionUsageException("external-duration", "invalid syntax: " + externalDuration);
}
if (externalExtent != null) {
Integer[] minMax = new Integer[] { 2, 2 };
Object[] treatments = new Object[] { NegativeTreatment.Error, MixedUnitsTreatment.Error };
List<Length> lengths = new java.util.ArrayList<Length>();
Location location = new Location(null, null, null, null);
if (Lengths.isLengths(externalExtent, location, this, minMax, treatments, lengths)) {
for (Length l : lengths) {
if (l.getUnits() != Length.Unit.Pixel)
throw new InvalidOptionUsageException("external-extent", "must use pixel (px) unit only: " + externalExtent);
}
parsedExternalExtent = new double[] { lengths.get(0).getValue(), lengths.get(1).getValue() };
getExternalParameters().setParameter("externalExtent", parsedExternalExtent);
} else
throw new InvalidOptionUsageException("external-extent", "invalid syntax: " + externalExtent);
}
if (optionProcessor != null)
optionProcessor.processDerivedOptions();
}
public void setShowOutput(PrintWriter showOutput) {
this.showOutput = showOutput;
}
private PrintWriter getShowOutput() {
if (showOutput == null)
showOutput = new PrintWriter(new OutputStreamWriter(System.err, defaultOutputEncoding));
return showOutput;
}
public void showBanner(PrintWriter out, String banner) {
if (!quiet)
out.println(banner);
}
private void showBanner(PrintWriter out, OptionProcessor optionProcessor) {
if (optionProcessor != null)
optionProcessor.showBanner(out);
else
showBanner(out, banner);
}
private void showUsage(PrintWriter out, OptionProcessor optionProcessor) {
showBanner(out, optionProcessor);
if (optionProcessor != null)
optionProcessor.showUsage(out);
else
showUsage(out);
}
private void showUsage(PrintWriter out) {
out.print("Usage: " + usageCommand + "\n");
showOptions(out, "Short Options", shortOptions);
showOptions(out, "Long Options", longOptions);
showOptions(out, "Non-Option Arguments", nonOptions);
}
public static void showOptions(PrintWriter out, String label, Collection<OptionSpecification> optionSpecs) {
StringBuffer sb = new StringBuffer();
sb.append(" ");
sb.append(label);
sb.append(':');
sb.append('\n');
for (OptionSpecification os : optionSpecs) {
sb.append(" ");
sb.append(os.toString());
sb.append('\n');
}
out.print(sb.toString());
}
public static void showOptions(PrintWriter out, String label, String[][] optionSpecs) {
StringBuffer sb = new StringBuffer();
sb.append(" ");
sb.append(label);
sb.append(':');
sb.append('\n');
for (String[] option : optionSpecs) {
assert option.length == 2;
sb.append(" ");
sb.append(option[0]);
for (int i = 0, n = OptionSpecification.OPTION_FIELD_LENGTH - option[0].length(); i < n; i++)
sb.append(' ');
sb.append('-');
sb.append(' ');
sb.append(option[1]);
sb.append('\n');
}
out.print(sb.toString());
}
private void showProcessingInfo() {
Reporter reporter = getReporter();
int level = reporter.getVerbosityLevel();
if (level > 0) {
if (level > 1)
showConfigurationInfo();
if (reporter.isTreatingWarningAsError())
reporter.logInfo(reporter.message("*KEY*", "Warnings are treated as errors."));
else if (reporter.areWarningsDisabled())
reporter.logInfo(reporter.message("*KEY*", "Warnings are disabled."));
else if (reporter.areWarningsHidden())
reporter.logInfo(reporter.message("*KEY*", "Warnings are hidden."));
}
}
private void showConfigurationInfo() {
Reporter reporter = getReporter();
if (configuration != null) {
URL locator = configuration.getLocator();
reporter.logInfo(reporter.message("*KEY*", "Loaded configuration from ''{0}''.", (locator != null) ? locator : "default empty configuration"));
if (!configuration.getOptions().isEmpty()) {
Map<String,String> options = configuration.getOptions();
Set<String> names = new java.util.TreeSet<String>(options.keySet());
for (String n : names) {
String v = options.get(n);
reporter.logInfo(reporter.message("*KEY*", "Configuration option: {0} = ''{1}''.", n, v));
}
} else
reporter.logInfo(reporter.message("*KEY*", "Configuration is empty."));
} else
reporter.logInfo(reporter.message("*KEY*", "No configuration."));
}
private void showModels() {
Reporter reporter = getReporter();
String defaultModelName = Models.getDefaultModelName();
StringBuffer sb = new StringBuffer();
sb.append("Verification Models:\n");
for (String modelName : Models.getModelNames()) {
sb.append(" ");
sb.append(modelName);
if (modelName.equals(defaultModelName)) {
sb.append(" (default)");
}
sb.append('\n');
if (reporter.getVerbosityLevel() > 0) {
Model model = Models.getModel(modelName);
String[] schemaResourceNames = model.getSchemaResourceNames();
if (schemaResourceNames != null) {
for (String schemaResourceName : schemaResourceNames) {
sb.append(" XSD: ");
sb.append(schemaResourceName);
sb.append('\n');
}
}
}
}
getShowOutput().println(sb.toString());
}
private void showRepository() {
getShowOutput().println(repositoryInfo);
}
private void showValidator() {
getShowOutput().println(getValidatorInfo());
}
private void showWarningTokens() {
Reporter reporter = getReporter();
int maxTokenLength = 0;
for (Object[] spec : defaultWarningSpecifications) {
String token = (String) spec[0];
int tokenLength = token.length();
maxTokenLength = Math.max(maxTokenLength, tokenLength + ((tokenLength % 2) + 1) * 2);
}
StringBuffer sb = new StringBuffer();
sb.append("Warning Tokens:\n");
for (Object[] spec : defaultWarningSpecifications) {
String token = (String) spec[0];
Boolean defaultValue = (Boolean) spec[1];
String help = (String) spec[2];
sb.append(" ");
sb.append(token);
if (reporter.getVerbosityLevel() > 0) {
if ((help != null) && (help.length() > 0)) {
int pad = maxTokenLength - token.length();
for (int i = 0; i < pad; ++i)
sb.append(' ');
sb.append(help);
if (!token.equals("all")) {
sb.append(" (default: ");
sb.append(defaultValue ? "enabled" : "disabled");
sb.append(')');
}
}
}
sb.append("\n");
}
getShowOutput().println(sb.toString());
}
private URI getCWDAsURI() {
return new File(".").toURI();
}
private URI resolve(String uriString) {
Reporter reporter = getReporter();
try {
URI uri = new URI(uriString);
if (!uri.isAbsolute()) {
URI uriCurrentDirectory = getCWDAsURI();
URI uriAbsolute = uriCurrentDirectory.resolve(uri);
assert uriAbsolute != null;
uri = uriAbsolute;
}
return uri;
} catch (URISyntaxException e) {
reporter.logError(reporter.message("*KEY*", "Bad URI syntax: '{'{0}'}'", uriString));
return null;
}
}
private ByteBuffer readResource(URI uri) {
Reporter reporter = getReporter();
ByteArrayOutputStream os = new ByteArrayOutputStream();
InputStream is = null;
try {
is = uri.toURL().openStream();
byte[] buffer = new byte[1024];
int nb;
while ((nb = is.read(buffer)) >= 0) {
os.write(buffer, 0, nb);
}
} catch (IOException e) {
reporter.logError(e);
os = null;
} finally {
IOUtil.closeSafely(is);
}
return (os != null) ? ByteBuffer.wrap(os.toByteArray()) : null;
}
public static Charset[] getPermittedEncodings() {
return permittedEncodings.toArray(new Charset[permittedEncodings.size()]);
}
private boolean isPermittedEncoding(String name) {
try {
Charset cs = Charset.forName(name);
for (Charset encoding : permittedEncodings) {
if (encoding.equals(cs))
return true;
}
return false;
} catch (UnsupportedCharsetException e) {
return false;
}
}
private CharBuffer decodeResource(ByteBuffer rawBuffer, Charset encoding, int bomLength) {
Reporter reporter = getReporter();
ByteBuffer bb = rawBuffer;
bb.position(bomLength);
List<CharBuffer> charBuffers = new java.util.ArrayList<CharBuffer>();
do {
CharsetDecoder cd = encoding.newDecoder();
boolean endOfInput = false;
CharBuffer cb = null;
CoderResult r;
while (true) {
try {
if (cb == null)
cb = CharBuffer.allocate(65536);
if (bb != null)
r = cd.decode(bb, cb, endOfInput);
else
r = cd.flush(cb);
if (r.isOverflow()) {
cb.flip();
charBuffers.add(cb);
cb = null;
} else if (r.isUnderflow()) {
if (endOfInput) {
if (bb != null)
bb = null;
else {
cb.flip();
charBuffers.add(cb);
cb = null;
break;
}
} else {
endOfInput = true;
}
} else if (r.isMalformed()) {
Message message = reporter.message("*KEY*",
"Malformed {0} at byte offset {1}{2,choice,0# of zero bytes|1# of one byte|1< of {2,number,integer} bytes}.",
encoding.name(), bb.position(), r.length());
reporter.logError(message);
return null;
} else if (r.isUnmappable()) {
Message message = reporter.message("*KEY*",
"Unmappable {0} at byte offset {1}{2,choice,0# of zero bytes|1# of one byte|1< of {2,number,integer} bytes}.",
encoding.name(), bb.position(), r.length());
reporter.logError(message);
return null;
} else if (r.isError()) {
Message message = reporter.message("*KEY*",
"Can't decode as {0} at byte offset {1}{2,choice,0# of zero bytes|1# of one byte|1< of {2,number,integer} bytes}.",
encoding.name(), bb.position(), r.length());
reporter.logError(message);
return null;
}
} catch (Exception e) {
reporter.logError(e);
return null;
}
}
} while (false);
rawBuffer.rewind();
return concatenateBuffers(charBuffers);
}
private static CharBuffer concatenateBuffers(List<CharBuffer> buffers) {
int length = 0;
for (CharBuffer cb : buffers) {
length += cb.limit();
}
CharBuffer newBuffer = CharBuffer.allocate(length);
for (CharBuffer cb : buffers) {
newBuffer.put(cb);
}
newBuffer.flip();
return newBuffer;
}
private String[] parseLines(CharBuffer cb, Charset encoding) {
List<String> lines = new java.util.ArrayList<String>();
StringBuffer sb = new StringBuffer();
while (cb.hasRemaining()) {
while (cb.hasRemaining()) {
char c = cb.get();
if (c == '\n') {
break;
} else if (c == '\r') {
if (cb.hasRemaining()) {
c = cb.charAt(0);
if (c == '\n')
cb.get();
}
break;
} else {
sb.append(c);
}
}
lines.add(sb.toString());
sb.setLength(0);
}
cb.rewind();
return lines.toArray(new String[lines.size()]);
}
public Map<String,Object> getResourceState() {
return resourceState;
}
private void setResourceURI(String uri) {
resourceUriString = uri;
getReporter().setResourceURI(uri);
}
private void setResourceURI(URI uri) {
resourceUri = uri;
// record as resource state
setResourceState("sysid", uri);
// record in reporter
getReporter().setResourceURI(uri);
}
private void setResourceBuffer(Charset encoding, CharBuffer buffer, ByteBuffer bufferRaw) {
resourceEncoding = encoding;
setResourceState("encoding", encoding);
resourceBufferRaw = bufferRaw;
setResourceState("bufferRaw", bufferRaw);
if (expectedErrors != null) {
resourceExpectedErrors = parseAnnotationAsInteger(expectedErrors, -1);
setResourceState("resourceExpectedErrors", Integer.valueOf(resourceExpectedErrors));
}
if (expectedWarnings != null) {
resourceExpectedWarnings = parseAnnotationAsInteger(expectedWarnings, -1);
setResourceState("resourceExpectedWarnings", Integer.valueOf(resourceExpectedWarnings));
}
}
private void setResourceDocumentContextState() {
if (parsedExternalExtent != null) {
setResourceState("externalExtent", parsedExternalExtent);
}
}
private boolean verifyResource() {
Reporter reporter = getReporter();
currentPhase = Phase.Resource;
if (!lastPhase.isEnabled(Phase.Resource)) {
reporter.logInfo(reporter.message("*KEY*", "Skipping resource presence and encoding verification phase {0}.", currentPhase.ordinal()));
return true;
} else {
reporter.logInfo(reporter.message("*KEY*", "Verifying resource presence and encoding phase {0}...", currentPhase.ordinal()));
}
URI uri = resolve(resourceUriString);
if (uri != null) {
setResourceURI(uri);
ByteBuffer bytesBuffer = readResource(uri);
if (bytesBuffer != null) {
Object[] sniffOutputParameters = new Object[] { Integer.valueOf(0) };
Charset encoding;
if (this.forceEncoding != null)
encoding = this.forceEncoding;
else
encoding = Sniffer.sniff(bytesBuffer, asciiEncoding, sniffOutputParameters);
if (isPermittedEncoding(encoding.name())) {
int bomLength = (Integer) sniffOutputParameters[0];
CharBuffer charsBuffer = decodeResource(bytesBuffer, encoding, bomLength);
if (charsBuffer != null) {
setResourceBuffer(encoding, charsBuffer, bytesBuffer);
if (includeSource)
reporter.setLines(parseLines(charsBuffer, encoding));
if (this.forceEncoding != null)
reporter.logInfo(reporter.message("*KEY*", "Resource encoding forced to {0}.", encoding.name()));
else
reporter.logInfo(reporter.message("*KEY*", "Resource encoding sniffed as {0}.", encoding.name()));
reporter.logInfo(reporter.message("*KEY*", "Resource length {0} bytes, decoded as {1} Java characters (char).",
bytesBuffer.limit(), charsBuffer.limit()));
}
} else {
reporter.logError(reporter.message("*KEY*", "Encoding {0} is not permitted", encoding.name()));
}
}
}
return reporter.getResourceErrors() == 0;
}
private InputStream openStream(ByteBuffer bb) {
bb.rewind();
if (bb.hasArray()) {
return new ByteArrayInputStream(bb.array());
} else {
byte[] bytes = new byte[bb.limit()];
bb.get(bytes);
return new ByteArrayInputStream(bb.array());
}
}
private void processAnnotations(Attributes attributes) {
Reporter reporter = getReporter();
boolean configureReporter = false;
if (attributes != null) {
String annotationsNamespace = Annotations.getNamespace();
// first process model annotation, as processing others may depend on this
for (int i = 0, n = attributes.getLength(); i < n; ++i) {
if (attributes.getURI(i).equals(annotationsNamespace)) {
String localName = attributes.getLocalName(i);
String value = attributes.getValue(i);
if (localName.equals("model")) {
Model model = Models.getModel(value);
if (model != null) {
resourceModel = model; configureReporter = true;
} else
throw new InvalidAnnotationException(localName, "unknown model '" + value + "'");
}
}
}
if (configureReporter)
resourceModel.configureReporter(reporter);
// now process other annotations
for (int i = 0, n = attributes.getLength(); i < n; ++i) {
if (attributes.getURI(i).equals(annotationsNamespace)) {
String localName = attributes.getLocalName(i);
String value = attributes.getValue(i);
if (localName.equals("expectedErrors")) {
resourceExpectedErrors = parseAnnotationAsInteger(value, -1);
} else if (localName.equals("expectedWarnings")) {
resourceExpectedWarnings = parseAnnotationAsInteger(value, -1);
} else if (localName.equals("model")) {
// no processing required here
} else if (localName.equals("warnOn")) {
String[] tokens = value.split("\\s+");
for (String token : tokens) {
if (reporter.hasDefaultWarning(token))
reporter.enableWarning(token);
}
} else if (localName.equals("noWarnOn")) {
String[] tokens = value.split("\\s+");
for (String token : tokens) {
if (reporter.hasDefaultWarning(token))
reporter.disableWarning(token);
}
} else if (localName.equals("loc")) {
// no processing required here
} else if (localName.equals("processingOptions")) {
if (!hasRestarted()) {
try {
setRestartOptions(RestartOptions.valueOf(value));
reporter.logInfo(reporter.message("*KEY*", "Found processing options, signalling restart: ''{0}''.", value));
} catch (RestartOptions.ParserException e) {
reporter.logError(reporter.message("*KEY*",
"Invalid processing options syntax for value ''{0}'': {1}.", value, e.getMessage()));
}
}
} else {
throw new InvalidAnnotationException(localName, "unknown annotation");
}
}
}
}
}
private void setRestartOptions(RestartOptions options) {
if (getRestartOptions() == null)
setResourceState("restartOptions", options);
}
private RestartOptions getRestartOptions() {
return (RestartOptions) getResourceState("restartOptions");
}
private boolean hasRestartOptions() {
RestartOptions restartOptions = getRestartOptions();
return (restartOptions != null) && !restartOptions.isEmpty();
}
private boolean needsRestart() {
return !hasRestarted() && hasRestartOptions();
}
private void setRestarted() {
restarted = true;
}
private boolean hasRestarted() {
return restarted;
}
private void restart(OptionProcessor optionProcessor) {
getShowOutput().flush();
resetAllState(getReporter(), getShowOutput(), optionProcessor, true);
setRestarted();
}
private boolean verifyWellFormedness() {
Reporter reporter = getReporter();
currentPhase = Phase.WellFormedness;
if (!lastPhase.isEnabled(Phase.WellFormedness)) {
reporter.logInfo(reporter.message("*KEY*", "Skipping XML well-formedness verification phase ({0}).", currentPhase.ordinal()));
return true;
} else
reporter.logInfo(reporter.message("*KEY*", "Verifying XML well-formedness phase {0}...", currentPhase.ordinal()));
try {
SAXParserFactory pf = SAXParserFactory.newInstance();
pf.setValidating(false);
pf.setNamespaceAware(true);
SAXParser p = pf.newSAXParser();
Charset encoding = getEncoding();
InputSource is = new InputSource(new InputStreamReader(openStream(resourceBufferRaw), encoding));
is.setEncoding(encoding.name());
is.setSystemId(resourceUri.toString());
p.parse(is, new DefaultHandler() {
private boolean expectRootElement = true;
public void startElement(String nsUri, String localName, String qualName, Attributes attrs) throws SAXException {
if (expectRootElement) {
processAnnotations(attrs);
expectRootElement = false;
}
}
public void error(SAXParseException e) {
// ensure parsing is terminated on well-formedness error
getReporter().logError(e);
throw new WellFormednessErrorException(e);
}
public void fatalError(SAXParseException e) {
// ensure parsing is terminated on well-formedness error
getReporter().logError(e);
throw new WellFormednessErrorException(e);
}
public void warning(SAXParseException e) {
// ensure parsing is terminated on well-formedness warning treated as error
if (getReporter().logWarning(e))
throw new WellFormednessErrorException(e);
}
});
} catch (ParserConfigurationException e) {
reporter.logError(e);
} catch (WellFormednessErrorException e) {
// Already logged error via default handler overrides above.
} catch (SAXParseException e) {
// Already logged error via default handler overrides above.
} catch (SAXException e) {
reporter.logError(e);
} catch (InvalidAnnotationException e) {
reporter.logError(e);
} catch (IOException e) {
reporter.logError(e);
}
return (reporter.getResourceErrors() == 0) && !needsRestart();
}
private SchemaFactory getSchemaFactory() {
if (schemaFactory == null) {
schemaFactory = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI);
}
return schemaFactory;
}
private Source[] getSources(URL[] components) {
List<Source> sources = new java.util.ArrayList<Source>(components.length);
for ( URL url : components ) {
sources.add(new StreamSource(url.toExternalForm()));
}
return sources.toArray(new Source[sources.size()]);
}
private Schema loadSchema(List<URL> components) throws SchemaValidationErrorException {
Reporter reporter = getReporter();
SchemaFactory sf = getSchemaFactory();
sf.setErrorHandler(new ErrorHandler() {
public void error(SAXParseException e) {
getReporter().logError(e);
throw new SchemaValidationErrorException(e);
}
public void fatalError(SAXParseException e) {
getReporter().logError(e);
throw new SchemaValidationErrorException(e);
}
public void warning(SAXParseException e) {
if (getReporter().logWarning(e))
throw new SchemaValidationErrorException(e);
}
});
try {
// attempt to enable non-pool grammars, i.e., use of xsi:schemaLocation pool extensions
sf.setFeature("http://apache.org/xml/features/internal/validation/schema/use-grammar-pool-only", false);
nonPoolGrammarSupported = true;
} catch (SAXException e) {
nonPoolGrammarSupported = false;
}
try {
reporter.logDebug(reporter.message("*KEY*", "Loading (and validating) schema components at '{'{0}'}'...", components));
return sf.newSchema(getSources(components.toArray(new URL[components.size()])));
} catch (SAXException e) {
reporter.logError(e);
throw new SchemaValidationErrorException(e);
}
}
private Schema getSchema(List<URL> components) throws SchemaValidationErrorException {
if (!schemas.containsKey(components)) {
schemas.put(components, loadSchema(components));
}
return schemas.get(components);
}
private URL getSchemaResource(String resourceName) throws SchemaValidationErrorException {
Reporter reporter = getReporter();
reporter.logDebug(reporter.message("*KEY*", "Searching for built-in schema at {0}...", resourceName));
try {
URL urlSchema = null;
Enumeration<URL> resources = getClass().getClassLoader().getResources(resourceName);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
String urlPath = url.getPath();
if (urlPath.indexOf(resourceName) == (urlPath.length() - resourceName.length())) {
reporter.logDebug(reporter.message("*KEY*", "Found resource match at '{'{0}'}'.", url));
urlSchema = url;
break;
} else {
reporter.logDebug(reporter.message("*KEY*", "Skipping partial resource match at '{'{0}'}'.", url));
}
}
if (urlSchema == null) {
Message message = reporter.message("*KEY*:", "Can't find schema resource '{'{0}'}'.", resourceName);
reporter.logDebug(message);
throw new SchemaValidationErrorException(new MissingResourceException(message.toString(), getClass().getName(), resourceName));
}
return urlSchema;
} catch (IOException e) {
throw new SchemaValidationErrorException(e);
}
}
private Schema getSchema() throws SchemaValidationErrorException {
Reporter reporter = getReporter();
List<URL> schemaComponents = new java.util.ArrayList<URL>();
for (String name : getModel().getSchemaResourceNames())
schemaComponents.add(getSchemaResource(name));
for (String schemaResourceLocation : extensionSchemas.values()) {
URI uri = resolve(schemaResourceLocation);
if (uri != null) {
try {
schemaComponents.add(uri.toURL());
} catch (IOException e) {
reporter.logError(e);
}
}
}
return getSchema(schemaComponents);
}
private boolean verifyValidity() {
Reporter reporter = getReporter();
currentPhase = Phase.Validity;
if (!lastPhase.isEnabled(Phase.Validity)) {
reporter.logInfo(reporter.message("*KEY*", "Skipping XSD validity verification phase ({0}).", currentPhase.ordinal()));
return true;
} else
reporter.logInfo(reporter.message("*KEY*", "Verifying XSD validity phase {0}...", currentPhase.ordinal()));
try {
SAXParserFactory pf = SAXParserFactory.newInstance();
pf.setNamespaceAware(true);
XMLReader reader = pf.newSAXParser().getXMLReader();
XMLReader filter = new ForeignVocabularyFilter(reader, getModel().getNamespaceURIs(), extensionSchemas.keySet(), foreignTreatment);
Charset encoding = getEncoding();
InputSource is = new InputSource(new InputStreamReader(openStream(resourceBufferRaw), encoding));
is.setEncoding(encoding.name());
is.setSystemId(resourceUri.toString());
SAXSource source = new SAXSource(filter, is);
source.setSystemId(resourceUri.toString());
Validator v = getSchema().newValidator();
v.setErrorHandler(new ErrorHandler() {
public void error(SAXParseException e) {
// don't terminated validation on validation error
getReporter().logError(e);
}
public void fatalError(SAXParseException e) {
// don't terminated validation on validation error
getReporter().logError(e);
}
public void warning(SAXParseException e) {
// don't terminated validation on validation warning treated as error
getReporter().logWarning(e);
}
});
v.validate(source);
} catch (ParserConfigurationException e) {
reporter.logError(e);
} catch (SchemaValidationErrorException e) {
reporter.logError(e);
// } catch (ForeignVocabularyException e) {
// Already logged error via foreign vocabulary filter
} catch (SAXParseException e) {
// Already logged error via default handler overrides above.
} catch (SAXException e) {
reporter.logError(e);
} catch (IOException e) {
reporter.logError(e);
}
return reporter.getResourceErrors() == 0;
}
private static final String saxPropertyPrefix = "http://xml.org/sax/properties/";
private static final String[] saxPropertyNames = {
"declaration-handler",
"document-xml-version",
"dom-node",
"lexical-handler",
"xml-string",
};
private static final String saxFeaturePrefix = "http://xml.org/sax/features/";
private static final String[] saxFeatureNames = {
"allow-dtd-events-after-endDTD",
"external-general-entities",
"external-parameter-entities",
"is-standalone",
"lexical-handler/parameter-entities",
"namespace-prefixes",
"namespaces",
"resolve-dtd-uris",
"string-interning",
"unicode-normalization-checking",
"use-attributes2",
"use-entity-resolver2",
"use-locator2",
"validation",
"xml-1.1",
"xmlns-uris",
};
private static final String jaxpPropertyPrefix = "http://java.sun.com/xml/jaxp/properties/";
private static final String[] jaxpPropertyNames = {
"schemaLanguage",
"schemaSource",
"elementAttributeLimit",
};
private static final String jaxpFeaturePrefix = "http://java.sun.com/xml/jaxp/features/";
private static final String[] jaxpFeatureNames = {
};
private static final String xercesPropertyPrefix = "http://apache.org/xml/properties/";
private static final String[] xercesPropertyNames = {
"dom/current-element-node",
"dom/document-class-name",
"input-buffer-size",
"internal/datatype-validator-factory",
"internal/document-scanner",
"internal/dtd-processor",
"internal/dtd-scanner",
"internal/entity-manager",
"internal/entity-resolver",
"internal/error-handler",
"internal/error-reporter",
"internal/grammar-pool",
"internal/namespace-binder",
"internal/namespace-context",
"internal/symbol-table",
"internal/validation-manager",
"internal/validator",
"internal/validator/dtd",
"internal/validator/schema",
"internal/xinclude-handler",
"internal/xpointer-handler",
"schema/external-noNamespaceSchemaLocation",
"schema/external-schemaLocation",
"security-manager",
};
private static final String xercesFeaturePrefix = "http://apache.org/xml/features/";
private static final String[] xercesFeatureNames = {
"allow-java-encodings",
"continue-after-fatal-error",
"disallow-doctype-decl",
"dom/create-entity-ref-nodes",
"dom/defer-node-expansion",
"dom/include-ignorable-whitespace",
"generate-synthetic-annotations",
"honour-all-schemaLocations",
"internal/parser-settings",
"internal/validation/schema/use-grammar-pool-only",
"nonvalidating/load-dtd-grammar",
"nonvalidating/load-external-dtd",
"scanner/notify-builtin-refs",
"scanner/notify-char-refs",
"standard-uri-conformant",
"validate-annotations",
"validation/change-ignorable-characters-into-ignorable-whitespaces",
"validation/default-attribute-values",
"validation/dynamic",
"validation/schema",
"validation/schema-full-checking",
"validation/schema/augment-psvi",
"validation/schema/element-default",
"validation/schema/ignore-schema-location-hints",
"validation/schema/normalized-value",
"validation/validate-content-models",
"validation/validate-datatypes",
"validation/warn-on-duplicate-attdef",
"validation/warn-on-undeclared-elemdef",
"warn-on-duplicate-entitydef",
"xinclude",
"xinclude-aware",
"xinclude/fixup-base-uris",
"xinclude/fixup-language",
};
private String getValidatorInfo() {
StringBuffer sb = new StringBuffer();
Validator v = null;
try {
v = getSchema().newValidator();
} catch (Exception e) {
sb.append("Unable to obtain validator information!\n");
}
if (v != null) {
sb.append("Validator class: " + v.getClass().getName() + "\n");
for (String pn : saxPropertyNames) {
try {
String ppn = saxPropertyPrefix + pn;
Object pv = v.getProperty(ppn);
assert pv != null;
sb.append("SAX property: {" + ppn + "}: " + pv.toString() + "\n");
} catch (SAXNotRecognizedException e) {
continue;
} catch (SAXNotSupportedException e) {
continue;
}
}
for (String fn : saxFeatureNames) {
try {
String pfn = saxFeaturePrefix + fn;
Object pv = v.getFeature(pfn);
if (pv != null)
sb.append("SAX feature: {" + pfn + "}: " + pv.toString() + "\n");
} catch (SAXNotRecognizedException e) {
continue;
} catch (SAXNotSupportedException e) {
continue;
}
}
for (String pn : jaxpPropertyNames) {
try {
String ppn = jaxpPropertyPrefix + pn;
Object pv = v.getProperty(ppn);
assert pv != null;
sb.append("JAXP property: {" + ppn + "}: " + pv.toString() + "\n");
} catch (SAXNotRecognizedException e) {
continue;
} catch (SAXNotSupportedException e) {
continue;
}
}
for (String fn : jaxpFeatureNames) {
try {
String pfn = jaxpFeaturePrefix + fn;
Object fv = v.getFeature(pfn);
if (fv != null)
sb.append("JAXP feature: {" + pfn + "}: " + fv.toString() + "\n");
} catch (SAXNotRecognizedException e) {
continue;
} catch (SAXNotSupportedException e) {
continue;
}
}
for (String pn : xercesPropertyNames) {
try {
String ppn = xercesPropertyPrefix + pn;
Object pv = v.getProperty(ppn);
assert pv != null;
sb.append("XERCES property: {" + ppn + "}: " + pv.toString() + "\n");
} catch (SAXNotRecognizedException e) {
continue;
} catch (SAXNotSupportedException e) {
continue;
}
}
for (String fn : xercesFeatureNames) {
try {
String pfn = xercesFeaturePrefix + fn;
Object fv = v.getFeature(pfn);
if (fv != null)
sb.append("XERCES feature: {" + pfn + "}: " + fv.toString() + "\n");
} catch (SAXNotRecognizedException e) {
continue;
} catch (SAXNotSupportedException e) {
continue;
}
}
}
return sb.toString();
}
private QName getXmlElementDecl(Class<?> jaxbClass, String creatorMethod) {
try {
Class<?> ofc = jaxbClass.getClassLoader().loadClass(jaxbClass.getPackage().getName() + ".ObjectFactory");
return ((JAXBElement<?>) ofc.getDeclaredMethod(creatorMethod, jaxbClass).invoke(ofc.newInstance(), new Object[] { null } )).getName();
} catch (ClassNotFoundException e) {
return emptyName;
} catch (IllegalAccessException e) {
return emptyName;
} catch (InstantiationException e) {
return emptyName;
} catch (InvocationTargetException e) {
return emptyName;
} catch (NoSuchMethodException e) {
return emptyName;
}
}
private String getContentClassNames(Map<Class<?>,String> contentClasses) {
StringBuffer sb = new StringBuffer();
sb.append('{');
for (Map.Entry<Class<?>,String> e : contentClasses.entrySet()) {
Class<?> contentClass = e.getKey();
if (sb.length() > 0)
sb.append(",");
sb.append('<');
sb.append(getXmlElementDecl(contentClass, e.getValue()));
sb.append('>');
}
sb.append('}');
return sb.toString();
}
private boolean verifyRootElement(JAXBElement<?> root, Map<Class<?>,String> rootClasses) {
Reporter reporter = getReporter();
Object contentObject = root.getValue();
for (Class<?> rootClass : rootClasses.keySet()) {
if (rootClass.isInstance(contentObject))
return true;
}
reporter.logError(reporter.message(Locators.getLocator(contentObject, resourceUriString), "*KEY*",
"Unexpected root element <{0}>, expected one of {1}.", root.getName(), getContentClassNames(rootClasses)));
return false;
}
private int parseAnnotationAsInteger(String annotation, int defaultValue) {
try {
return Integer.parseInt(annotation);
} catch (NumberFormatException e) {
return defaultValue;
}
}
private boolean verifySemantics() {
Reporter reporter = getReporter();
currentPhase = Phase.Semantics;
if (!lastPhase.isEnabled(Phase.Semantics)) {
reporter.logInfo(reporter.message("*KEY*", "Skipping semantics verification phase ({0}).", currentPhase.ordinal()));
return true;
} else
reporter.logInfo(reporter.message("*KEY*", "Verifying semantics phase {0} using ''{1}'' model...", currentPhase.ordinal(), getModel().getName()));
try {
// construct source pipeline
SAXParserFactory pf = SAXParserFactory.newInstance();
pf.setNamespaceAware(true);
XMLReader reader = pf.newSAXParser().getXMLReader();
ForeignVocabularyFilter filter1 = new ForeignVocabularyFilter(reader, getModel().getNamespaceURIs(), extensionSchemas.keySet(), ForeignTreatment.Allow);
LocationAnnotatingFilter filter2 = new LocationAnnotatingFilter(filter1);
Charset encoding = getEncoding();
InputSource is = new InputSource(new InputStreamReader(openStream(resourceBufferRaw), encoding));
is.setEncoding(encoding.name());
is.setSystemId(resourceUri.toString());
SAXSource source = new SAXSource(filter2, is);
source.setSystemId(resourceUri.toString());
// construct annotated infoset destination
DOMResult result = new DOMResult();
result.setSystemId(resourceUri.toString());
// transform into annotated infoset
TransformerFactory tf = TransformerFactory.newInstance();
tf.newTransformer().transform(source, result);
// unmarshall annotated infoset
JAXBContext context = JAXBContext.newInstance(getModel().getJAXBContextPath());
Binder<Node> binder = context.createBinder();
Object unmarshalled = binder.unmarshal(result.getNode());
// retain reference to binder at instance scope for error reporter utilities
this.binder = binder;
// verify root then remaining semantics
if (unmarshalled == null)
reporter.logError(reporter.message("*KEY*", "Missing root element."));
else if (!(unmarshalled instanceof JAXBElement))
reporter.logError(reporter.message("*KEY*", "Unexpected root element, can't introspect non-JAXBElement"));
else {
JAXBElement<?> root = (JAXBElement<?>) unmarshalled;
Documents.assignIdAttributes(binder.getXMLNode(root).getOwnerDocument(), getModel().getIdAttributes());
if (verifyRootElement(root, getModel().getRootClasses())) {
this.rootBinding = root.getValue();
this.rootName = root.getName();
setResourceDocumentContextState();
getModel().getSemanticsVerifier().verify(this.rootBinding, this);
}
}
} catch (UnmarshalException e) {
reporter.logError(e);
} catch (TransformerFactoryConfigurationError e) {
reporter.logError(new Exception(e));
} catch (ParserConfigurationException e) {
reporter.logError(e);
} catch (TransformerConfigurationException e) {
reporter.logError(e);
} catch (TransformerException e) {
reporter.logError(e);
} catch (JAXBException e) {
reporter.logError(e);
} catch (SAXException e) {
reporter.logError(e);
}
return reporter.getResourceErrors() == 0;
}
private int verify(List<String> args, String uri, ResultProcessor resultProcessor) {
Reporter reporter = getReporter();
if (!reporter.isHidingLocation())
reporter.logInfo(reporter.message("*KEY*", "Verifying '{'{0}'}'.", uri));
do {
resetResourceState();
setResourceURI(uri);
if (!verifyResource())
break;
if (!verifyWellFormedness())
break;
if (!verifyValidity())
break;
if (!verifySemantics())
break;
if (resultProcessor != null)
resultProcessor.processResult(args, resourceUri, rootBinding);
} while (false);
int rv = rvValue();
if (rvCode(rv) != RV_RESTART) {
reporter.logInfo(reporter.message("*KEY*", "Verification {0}{1}.", rvPassed(rv) ? "Passed" : "Failed", resultDetails()));
reporter.flush();
this.results.put(uri,
new Results(uri, rv, resourceExpectedErrors, reporter.getResourceErrors(), resourceExpectedWarnings,
reporter.getResourceWarnings(), getModel(), getEncoding(), rootName));
}
return rv;
}
private int rvValue() {
Reporter reporter = getReporter();
int code = RV_PASS;
int flags = 0;
if (needsRestart())
return RV_RESTART;
if (resourceExpectedErrors < 0) {
if (reporter.getResourceErrors() > 0) {
code = RV_FAIL;
flags |= RV_FLAG_ERROR_UNEXPECTED;
}
} else if (reporter.getResourceErrors() != resourceExpectedErrors) {
code = RV_FAIL;
flags |= RV_FLAG_ERROR_EXPECTED_MISMATCH;
} else {
code = RV_PASS;
if (reporter.getResourceErrors() > 0)
flags |= RV_FLAG_ERROR_EXPECTED_MATCH;
}
if (resourceExpectedWarnings < 0) {
if (reporter.getResourceWarnings() > 0)
flags |= RV_FLAG_WARNING_UNEXPECTED;
} else if (reporter.getResourceWarnings() != resourceExpectedWarnings) {
code = RV_FAIL;
flags |= RV_FLAG_WARNING_EXPECTED_MISMATCH;
} else {
if (reporter.getResourceWarnings() > 0)
flags |= RV_FLAG_WARNING_EXPECTED_MATCH;
}
return ((flags & 0x7FFFFF) << 8) | (code & 0xFF);
}
public static boolean rvPassed(int rv) {
return rvCode(rv) == RV_PASS;
}
public static int rvCode(int rv) {
return (rv & 0xFF);
}
public static int rvFlags(int rv) {
return ((rv >> 8) & 0x7FFFFF);
}
private String resultDetails() {
Reporter reporter = getReporter();
int resourceErrors = reporter.getResourceErrors();
int resourceWarnings = reporter.getResourceWarnings();
StringBuffer details = new StringBuffer();
if (resourceExpectedErrors < 0) {
if (resourceErrors > 0) {
details.append(", with " );
details.append(resourceErrors);
details.append(' ');
details.append(plural("error", resourceErrors));
}
} else if (resourceErrors == resourceExpectedErrors) {
if (resourceErrors > 0) {
details.append(", with ");
details.append(resourceErrors);
details.append(" expected ");
details.append(plural("error", resourceErrors));
}
} else {
details.append(", with ");
details.append(resourceErrors);
details.append(' ');
details.append(plural("error", resourceErrors));
details.append(" but expected ");
details.append(resourceExpectedErrors);
details.append(' ');
details.append(plural("error", resourceExpectedErrors));
}
if (resourceExpectedWarnings < 0) {
if (resourceWarnings > 0) {
details.append(details.length() > 0 ? ", and with " : ", with ");
details.append(resourceWarnings);
details.append(' ');
details.append(plural("warning", resourceWarnings));
}
} else if (resourceWarnings == resourceExpectedWarnings) {
if (resourceWarnings > 0) {
details.append(details.length() > 0 ? ", and with " : ", with ");
details.append(resourceWarnings);
details.append(" expected ");
details.append(plural("warning", resourceWarnings));
}
} else {
details.append(details.length() > 0 ? ", and with " : ", with ");
details.append(resourceWarnings);
details.append(' ');
details.append(plural("warning", resourceWarnings));
details.append(" but expected ");
details.append(resourceExpectedWarnings);
details.append(' ');
details.append(plural("warning", resourceExpectedWarnings));
}
return details.toString();
}
private String plural(String noun, int count) {
if (count == 1)
return noun;
else
return noun + "s";
}
private int verify(List<String> args, List<String> nonOptionArgs, ResultProcessor resultProcessor) {
Reporter reporter = getReporter();
int numFailure = 0;
int numSuccess = 0;
for (String uri : nonOptionArgs) {
switch (rvCode(verify(args, maybeConvertToFileURLString(uri), resultProcessor))) {
case RV_PASS:
++numSuccess;
break;
case RV_FAIL:
++numFailure;
break;
case RV_RESTART:
return RV_RESTART;
default:
break;
}
reporter.flush();
}
if (reporter.getVerbosityLevel() > 0) {
Message message;
if (numSuccess > 0) {
if (numFailure > 0) {
message = reporter.message("*KEY*",
"Passed {0} {0,choice,0#resources|1#resource|1<resources}, Failed {1} {1,choice,0#resources|1#resource|1<resources}.", numSuccess, numFailure);
} else {
message = reporter.message("*KEY*",
"Passed {0} {0,choice,0#resources|1#resource|1<resources}.", numSuccess);
}
} else {
if (numFailure > 0) {
message = reporter.message("*KEY*",
"Failed {0} {0,choice,0#resources|1#resource|1<resources}.", numFailure);
} else {
message = null;
}
}
if (message != null)
reporter.logInfo(message);
}
return numFailure > 0 ? 1 : 0;
}
public int run(String[] args) {
return run(Arrays.asList(args), null);
}
public int run(List<String> args, ResultProcessor resultProcessor) {
return run(args, resultProcessor, false);
}
private int run(List<String> args, ResultProcessor resultProcessor, boolean restarting) {
int rv = 0;
OptionProcessor optionProcessor = (OptionProcessor) resultProcessor;
try {
if (restarting)
restart(optionProcessor);
List<String> argsPreProcessed = preProcessOptions(args, optionProcessor);
List<String> nonOptionArgs = parseArgs(argsPreProcessed, optionProcessor);
if (!restarting) {
showBanner(getShowOutput(), optionProcessor);
getShowOutput().flush();
if (showModels)
showModels();
if (showRepository)
showRepository();
if (showValidator)
showValidator();
if (showWarningTokens)
showWarningTokens();
}
if (optionProcessor != null)
optionProcessor.runOptions(getShowOutput());
if (nonOptionArgs.size() > 1) {
if (expectedErrors != null)
throw new InvalidOptionUsageException("expect-errors", "must not specify more than one URL with this option");
if (expectedWarnings != null)
throw new InvalidOptionUsageException("expect-warnings", "must not specify more than one URL with this option");
}
getShowOutput().flush();
if (nonOptionArgs.size() > 0) {
showProcessingInfo();
rv = verify(args, nonOptionArgs, resultProcessor);
if (rv == RV_RESTART) {
assert !hasRestarted();
List<String> argsRestart = processRestartArguments(args, nonOptionArgs, resultProcessor);
reporter.logInfo(reporter.message("*KEY*", "Restarting with augmented option arguments: {0}.", argsRestart));
rv = run(argsRestart, resultProcessor, true);
}
} else
rv = RV_PASS;
} catch (ShowUsageException e) {
showUsage(getShowOutput(), optionProcessor);
rv = RV_USAGE;
} catch (UsageException e) {
getShowOutput().println("Usage: " + e.getMessage());
rv = RV_USAGE;
}
resetReporter();
getShowOutput().flush();
return rv;
}
public Map<String,Results> getResults() {
return results;
}
public Results getResults(String uri) {
return results.get(uri);
}
public int getResultCode(String uri) {
if (results.containsKey(uri))
return results.get(uri).code;
else
return -1;
}
public int getResultFlags(String uri) {
if (results.containsKey(uri))
return results.get(uri).flags;
else
return -1;
}
public static String getVersionTitle() {
return title;
}
public static String getRepositoryURL() {
return repositoryURL;
}
public static void main(String[] args) {
Runtime.getRuntime().exit(new TimedTextVerifier().run(args));
}
private static String maybeConvertToFileURLString(String s) {
if (isRelativeFilePath(s))
return convertRelativeFilePathToFileURLString(s);
else if (isAbsoluteFilePath(s))
return convertAbsoluteFilePathToFileURLString(s);
else
return s;
}
private static boolean isRelativeFilePath(String s) {
String rp1 = new String(new char[]{'.',File.separatorChar});
if (s.startsWith(rp1))
return true;
String rp2 = new String(new char[]{'.','.',File.separatorChar});
if (s.startsWith(rp2))
return true;
return false;
}
private static String convertRelativeFilePathToFileURLString(String s) {
return s;
}
private static boolean isAbsoluteFilePath(String s) {
return s.startsWith(File.separator);
}
private static String convertAbsoluteFilePathToFileURLString(String s) {
return "file://" + escapeURL(s);
}
private static String escapeURL(String s) {
StringBuffer sb = new StringBuffer();
for (int i = 0, n = s.length(); i < n; ++i) {
char c = s.charAt(i);
if (Character.isWhitespace(c))
sb.append("%20");
else
sb.append(c);
}
return sb.toString();
}
private static String[] externalRepresentations(URI[] uris) {
List<String> uriStrings = new java.util.ArrayList<String>(uris.length);
for (URI uri : uris) {
uriStrings.add(uri.toString());
}
return uriStrings.toArray(new String[uriStrings.size()]);
}
private static class InvalidAnnotationException extends RuntimeException {
static final long serialVersionUID = 0;
InvalidAnnotationException(String name, String details) {
super("invalid annotation: " + name + ": " + details);
}
}
private static class WellFormednessErrorException extends RuntimeException {
static final long serialVersionUID = 0;
WellFormednessErrorException(Throwable cause) {
super(cause);
}
}
private static class SchemaValidationErrorException extends RuntimeException {
static final long serialVersionUID = 0;
SchemaValidationErrorException(Throwable cause) {
super(cause);
}
}
private class ForeignVocabularyFilter extends XMLFilterImpl {
private Set<String> standardNamespaces;
private Set<String> extensionNamespaces;
private ForeignTreatment foreignTreatment;
private Stack<QName> nameStack = new Stack<QName>();
private boolean inForeign;
private Locator currentLocator;
ForeignVocabularyFilter(XMLReader reader, String[] standardNamespaces, Set<String> extensionNamespaces, ForeignTreatment foreignTreatment) {
super(reader);
this.standardNamespaces = new java.util.HashSet<String>(Arrays.asList(standardNamespaces));
this.extensionNamespaces = new java.util.HashSet<String>(extensionNamespaces);
this.foreignTreatment = foreignTreatment;
}
ForeignVocabularyFilter(XMLReader reader, URI[] namespaceURIs, Set<String> extensionNamespaces, ForeignTreatment foreignTreatment) {
this(reader, externalRepresentations(namespaceURIs), extensionNamespaces, foreignTreatment);
}
@Override
public void setDocumentLocator(Locator locator) {
super.setDocumentLocator(locator);
currentLocator = locator;
}
@Override
public void startElement(String nsUri, String localName, String qualName, Attributes attrs) throws SAXException {
Reporter reporter = getReporter();
if (foreignTreatment == ForeignTreatment.Allow)
super.startElement(nsUri, localName, qualName, attrs);
else if (!inForeign && isNonForeignNamespace(nsUri))
super.startElement(nsUri, localName, qualName, filterAttributes(attrs));
else {
QName qn = new QName(nsUri, localName);
nameStack.push(qn);
inForeign = true;
logPruning(reporter.message(currentLocator, "*KEY*", "Pruning element in foreign namespace '{'{0}'}'.", qn));
}
}
@Override
public void characters(char[] chars, int start, int length) throws SAXException {
if (!inForeign)
super.characters(chars, start, length);
}
@Override
public void endElement(String nsUri, String localName, String qualName) throws SAXException {
if (!inForeign) {
super.endElement(nsUri, localName, qualName);
} else if (!nameStack.empty()) {
QName name = new QName(nsUri, localName);
if (nameStack.peek().equals(name)) {
nameStack.pop();
if (nameStack.empty())
inForeign = false;
} else {
throw new IllegalStateException();
}
} else {
throw new IllegalStateException();
}
}
private Attributes filterAttributes(Attributes attrs) {
Reporter reporter = getReporter();
boolean hasForeign = false;
for (int i = 0, n = attrs.getLength(); i < n; ++i) {
String nsUri = attrs.getURI(i);
if (isNonForeignNamespace(nsUri)) {
if (XML.isXSINamespace(nsUri))
checkXSIAttribute(attrs, i);
continue;
} else
hasForeign = true;
}
if (hasForeign && (foreignTreatment != ForeignTreatment.Allow)) {
AttributesImpl attrsNew = new AttributesImpl();
for (int i = 0, n = attrs.getLength(); i < n; ++i) {
String nsUri = attrs.getURI(i);
if (isNonForeignNamespace(nsUri))
attrsNew = copyAttribute(attrsNew, attrs, i);
else {
logPruning(reporter.message(currentLocator, "*KEY*",
"Pruning attribute in foreign namespace '{'{0}'}'.", new QName(attrs.getURI(i), attrs.getLocalName(i))));
}
}
return attrsNew;
} else
return attrs;
}
private AttributesImpl copyAttribute(AttributesImpl attrsNew, Attributes attrsOld, int i) {
attrsNew.addAttribute(attrsOld.getURI(i), attrsOld.getLocalName(i), attrsOld.getQName(i), attrsOld.getType(i), attrsOld.getValue(i));
return attrsNew;
}
private void checkXSIAttribute(Attributes attrs, int index) {
Reporter reporter = getReporter();
QName qn = new QName(attrs.getURI(index), attrs.getLocalName(index));
String ln = qn.getLocalPart();
if (ln.equals("schemaLocation")) {
String locations = attrs.getValue(index);
if (reporter.isWarningEnabled("xsi-schema-location")) {
Message message = reporter.message(currentLocator, "*KEY*",
"An '{'{0}'}' attribute was used, with value '{'{1}'}'.", qn, locations);
if (reporter.logWarning(message))
reporter.logError(message);
}
checkXSISchemaLocations(qn, locations);
} else if (ln.equals("noNamespaceSchemaLocation")) {
if (reporter.isWarningEnabled("xsi-no-namespace-schema-location")) {
Message message = reporter.message(currentLocator, "*KEY*",
"An '{'{0}'}' attribute was used, but foreign vocabulary in no namespace is not supported.", qn);
if (reporter.logWarning(message))
reporter.logError(message);
}
} else if (ln.equals("type") || ln.equals("nil")) {
if (reporter.isWarningEnabled("xsi-other-attribute")) {
Message message = reporter.message(currentLocator, "*KEY*",
"An '{'{0}'}' attribute was used, but may not be supported by platform supplied validator.", qn);
if (reporter.logWarning(message))
reporter.logError(message);
}
} else
reporter.logError(reporter.message(currentLocator, "*KEY*", "Unknown attribute '{'{0}'}' in XSI namespace.", qn));
}
private void checkXSISchemaLocations(QName qn, String locations) {
Reporter reporter = getReporter();
String[] pairs = locations.trim().split("\\s+");
if ((pairs.length & 1) != 0) {
reporter.logError(reporter.message(currentLocator, "*KEY*", "Unpaired schema location in '{'{0}'}': '{'{1}'}'.", qn, locations));
} else {
for (int i = 0, n = pairs.length / 2; i < n; ++i) {
String schemaNamespace = pairs[2*i+0];
if (!nonPoolGrammarSupported && !extensionSchemas.containsKey(schemaNamespace)) {
if (reporter.isWarningEnabled("xsi-schema-location")) {
Message message = reporter.message(currentLocator, "*KEY*",
"Platform validator doesn't support non-pool schemas, try specifying location {0} with ''--external-schema'' option.", schemaNamespace);
if (reporter.logWarning(message))
reporter.logError(message);
}
}
}
}
}
private boolean isNonForeignNamespace(String nsUri) {
if (nsUri == null)
return true;
else if (nsUri.length() == 0)
return true;
else if (XML.isXMLNamespace(nsUri))
return true;
else if (XML.isXSINamespace(nsUri))
return true;
else if (isStandardNamespace(nsUri))
return true;
else if (isExtensionNamespace(nsUri))
return true;
else if (isAnnotationNamespace(nsUri))
return true;
else
return false;
}
private boolean isStandardNamespace(String nsUri) {
return standardNamespaces.contains(nsUri);
}
private boolean isExtensionNamespace(String nsUri) {
return extensionNamespaces.contains(nsUri);
}
private boolean isAnnotationNamespace(String nsUri) {
if (nsUri.equals(Annotations.getNamespace()))
return true;
else
return false;
}
private void logPruning(Message message) {
Reporter reporter = getReporter();
if (foreignTreatment == ForeignTreatment.Error) {
reporter.logError(message);
} if (foreignTreatment == ForeignTreatment.Warning) {
if (reporter.isWarningEnabled("foreign")) {
if (reporter.logWarning(message))
reporter.logError(message);
}
} else if (foreignTreatment == ForeignTreatment.Info)
reporter.logInfo(message);
}
}
private static class LocationAnnotatingFilter extends XMLFilterImpl {
private Locator currentLocator;
LocationAnnotatingFilter(XMLReader reader) {
super(reader);
}
@Override
public void setDocumentLocator(Locator locator) {
super.setDocumentLocator(locator);
currentLocator = locator;
}
@Override
public void startElement(String nsUri, String localName, String qualName, Attributes attrs) throws SAXException {
super.startElement(nsUri, localName, qualName, addLocationAttribute(attrs));
}
private final QName qnLoc = Locators.getLocatorAttributeQName();
private String qnLocQualName = qnLoc.getPrefix() + ":" + qnLoc.getLocalPart();
private boolean rootElement = true;
private Attributes addLocationAttribute(Attributes attrs) {
if (currentLocator != null) {
AttributesImpl attrsNew = new AttributesImpl(attrs);
StringBuilder sb = new StringBuilder();
if (rootElement) {
sb.append('{');
sb.append(currentLocator.getSystemId());
sb.append('}');
rootElement = false;
} else
sb.append("{}");
sb.append(':');
sb.append(Integer.toString(currentLocator.getLineNumber()));
sb.append(':');
sb.append(Integer.toString(currentLocator.getColumnNumber()));
attrsNew.addAttribute(qnLoc.getNamespaceURI(), qnLoc.getLocalPart(), qnLocQualName, "CDATA", sb.toString());
return attrsNew;
} else
return attrs;
}
}
public static class Results {
private static final String NOURI = "*URI NOT AVAILABLE*";
private static final String NOENCODING = "*ENCODING NOT AVAILABLE*";
private static final String NOMODEL = "*MODEL NOT AVAILABLE*";
private String uriString;
private boolean succeeded;
private int code;
private int flags;
private int errorsExpected;
private int errors;
private int warningsExpected;
private int warnings;
private String modelName;
private String encodingName;
private QName root;
public Results() {
this.uriString = NOURI;
this.succeeded = false;
this.code = RV_USAGE;
this.modelName = NOMODEL;
this.encodingName = NOENCODING;
}
public Results(String uriString, int rv, int errorsExpected, int errors, int warningsExpected, int warnings, Model model, Charset encoding, QName root) {
this.uriString = uriString;
this.succeeded = rvPassed(rv);
this.code = rvCode(rv);
this.flags = rvFlags(rv);
this.errorsExpected = errorsExpected;
this.errors = errors;
this.warningsExpected = warningsExpected;
this.warnings = warnings;
if (model != null)
this.modelName = model.getName();
else
this.modelName = "unknown";
if (encoding != null)
this.encodingName = encoding.name();
else
this.encodingName = "unknown";
this.root = root;
}
public String getURIString() {
return uriString;
}
public boolean getSucceeded() {
return succeeded;
}
public int getCode() {
return code;
}
public int getFlags() {
return flags;
}
public int getErrorsExpected() {
return errorsExpected;
}
public int getErrors() {
return errors;
}
public int getWarningsExpected() {
return warningsExpected;
}
public int getWarnings() {
return warnings;
}
public String getModelName() {
return modelName;
}
public String getEncodingName() {
return encodingName;
}
public QName getRoot() {
return root;
}
}
public static class ExternalParametersStore implements ExternalParameters {
private Map<String, Object> parameters = new java.util.HashMap<String, Object>();
public Object getParameter(String name) {
return parameters.get(name);
}
public Object setParameter(String name, Object value) {
return parameters.put(name, value);
}
}
}