/*
* Copyright (c) 2016 Google Inc.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.google.eclipse.protobuf.validation;
import static org.eclipse.xtext.diagnostics.Severity.ERROR;
import static org.eclipse.xtext.diagnostics.Severity.WARNING;
import static org.eclipse.xtext.validation.AbstractInjectableValidator.CURRENT_LANGUAGE_NAME;
import static org.eclipse.xtext.validation.CancelableDiagnostician.CANCEL_INDICATOR;
import static org.eclipse.xtext.validation.CheckMode.KEY;
import static org.eclipse.xtext.validation.CheckType.FAST;
import static org.eclipse.xtext.validation.impl.ConcreteSyntaxEValidator.DISABLE_CONCRETE_SYNTAX_EVALIDATOR;
import static com.google.eclipse.protobuf.util.Tracer.DEBUG_SCOPING;
import static com.google.eclipse.protobuf.util.Tracer.trace;
import static com.google.common.collect.Lists.newArrayListWithExpectedSize;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.eclipse.protobuf.validation.Messages.importingUnsupportedSyntax;
import static com.google.eclipse.protobuf.validation.Messages.scopingError;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EValidator;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xtext.diagnostics.Severity;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.util.CancelIndicator;
import org.eclipse.xtext.util.IAcceptor;
import org.eclipse.xtext.validation.CheckMode;
import org.eclipse.xtext.validation.Issue;
import org.eclipse.xtext.validation.ResourceValidatorImpl;
import com.google.eclipse.protobuf.linking.ProtobufDiagnostic;
import com.google.eclipse.protobuf.util.TimingCollector;
/**
* Adds support for converting scoping errors into warnings if non-proto2 files are imported.
*
* @author alruiz@google.com (Alex Ruiz)
*/
public class ProtobufResourceValidator extends ResourceValidatorImpl {
private static final Logger log = Logger.getLogger(ProtobufResourceValidator.class);
private static final ThreadLocal<TimingCollector> scopeProviderTimingCollector =
new ThreadLocal<TimingCollector>() {
@Override
public TimingCollector initialValue() {
return new TimingCollector();
}
};
@Override
public List<Issue> validate(Resource resource, CheckMode mode, CancelIndicator indicator) {
CancelIndicator monitor = indicator == null ? CancelIndicator.NullImpl : indicator;
getScopeProviderTimingCollector().clear();
resolveProxies(resource, monitor);
if (DEBUG_SCOPING) {
trace("Debugging AbstractDeclarativeScopeProvider.getScope() "
+ getScopeProviderTimingCollector().toString());
}
return handleIssues(resource, mode, monitor);
}
private List<Issue> handleIssues(Resource resource, CheckMode mode, CancelIndicator monitor) {
if (monitor.isCanceled()) {
return null;
}
List<Issue> result =
newArrayListWithExpectedSize(resource.getErrors().size() + resource.getWarnings().size());
try {
IAcceptor<Issue> acceptor = createAcceptor(result);
Status status = delegateValidationToDiagnostician(resource, mode, monitor, acceptor);
if (status.isCanceled()) {
return null;
}
if (mode.shouldCheck(FAST)) {
status = createErrors(resource, status.hasProto1Imports(), acceptor, monitor);
if (status.isCanceled()) {
return null;
}
status = createWarnings(resource, acceptor, monitor);
if (status.isCanceled()) {
return null;
}
}
} catch (RuntimeException e) {
log.error(e.getMessage(), e);
}
return result;
}
private Status delegateValidationToDiagnostician(
Resource resource, CheckMode mode, CancelIndicator monitor, IAcceptor<Issue> acceptor) {
Status hasNonProto2Import = Status.OK;
for (EObject element : resource.getContents()) {
if (monitor.isCanceled()) {
return Status.CANCELED;
}
Diagnostic diagnostic =
getDiagnostician().validate(element, validationOptions(resource, mode, monitor));
if (convertIssuesToMarkers(acceptor, diagnostic) == Status.PROTO1_IMPORTS_FOUND) {
hasNonProto2Import = Status.PROTO1_IMPORTS_FOUND;
}
}
return hasNonProto2Import;
}
private Map<Object, Object> validationOptions(
Resource resource, CheckMode mode, CancelIndicator monitor) {
Map<Object, Object> options = newHashMap();
options.put(KEY, mode);
options.put(CANCEL_INDICATOR, monitor);
options.put(DISABLE_CONCRETE_SYNTAX_EVALIDATOR, true);
options.put(EValidator.class, getDiagnostician());
if (resource instanceof XtextResource) {
options.put(CURRENT_LANGUAGE_NAME, ((XtextResource) resource).getLanguageName());
}
return options;
}
private Status convertIssuesToMarkers(IAcceptor<Issue> acceptor, Diagnostic diagnostic) {
Status hasNonProto2Import = Status.OK;
if (diagnostic.getChildren().isEmpty()) {
issueFromEValidatorDiagnostic(diagnostic, acceptor);
return hasNonProto2Import;
}
for (Diagnostic child : diagnostic.getChildren()) {
if (importingUnsupportedSyntax.equals(child.getMessage())) {
hasNonProto2Import = Status.PROTO1_IMPORTS_FOUND;
}
issueFromEValidatorDiagnostic(child, acceptor);
}
return hasNonProto2Import;
}
private Status createErrors(
Resource resource,
boolean proto1ImportsFound,
IAcceptor<Issue> acceptor,
CancelIndicator monitor) {
for (Resource.Diagnostic error : resource.getErrors()) {
if (monitor.isCanceled()) {
return Status.CANCELED;
}
Severity severity = ERROR;
if (proto1ImportsFound && isUnresolvedReferenceError(error)) {
severity = WARNING;
ProtobufDiagnostic d = (ProtobufDiagnostic) error;
String message = d.getMessage();
if (message.endsWith(scopingError)) {
continue;
}
if (!message.endsWith(".")) {
d.appendToMessage(".");
}
d.appendToMessage(" ");
d.appendToMessage(scopingError);
}
issueFromXtextResourceDiagnostic(error, severity, acceptor);
}
return Status.OK;
}
private boolean isUnresolvedReferenceError(Resource.Diagnostic error) {
if (!(error instanceof ProtobufDiagnostic)) {
return false;
}
ProtobufDiagnostic d = (ProtobufDiagnostic) error;
if ("org.eclipse.xtext.diagnostics.Diagnostic.Linking".equals(d.getCode())) {
return error.getMessage().startsWith("Couldn't resolve");
}
return false;
}
private Status createWarnings(
Resource resource, IAcceptor<Issue> acceptor, CancelIndicator monitor) {
for (Resource.Diagnostic warning : resource.getWarnings()) {
if (monitor.isCanceled()) {
return Status.CANCELED;
}
issueFromXtextResourceDiagnostic(warning, WARNING, acceptor);
}
return Status.OK;
}
private static enum Status {
OK,
CANCELED,
PROTO1_IMPORTS_FOUND;
boolean hasProto1Imports() {
return (this == PROTO1_IMPORTS_FOUND);
}
boolean isCanceled() {
return (this == CANCELED);
}
}
public static TimingCollector getScopeProviderTimingCollector() {
return scopeProviderTimingCollector.get();
}
}