AnnexParserAgent.java
/**
* Copyright (c) 2004-2025 Carnegie Mellon University and others. (see Contributors file).
* All Rights Reserved.
*
* NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE
* OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT
* MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
* SPDX-License-Identifier: EPL-2.0
*
* Created, in part, with funding and support from the United States Government. (see Acknowledgments file).
*
* This program includes and/or can make use of certain third party source code, object code, documentation and other
* files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system
* configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and
* conditions contained in any such Third Party Software or separate license file distributed with such Third Party
* Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici-
* aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li-
* censes only apply to the Third Party Software and not any other portion of this program or this program as a whole.
*/
package org.osate.xtext.aadl2.parsing;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import org.apache.commons.lang.StringUtils;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.diagnostics.Diagnostic;
import org.eclipse.xtext.diagnostics.IDiagnosticConsumer;
import org.eclipse.xtext.diagnostics.Severity;
import org.eclipse.xtext.linking.ILinker;
import org.eclipse.xtext.linking.lazy.LazyLinker;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.XtextResource;
import org.osate.aadl2.AadlPackage;
import org.osate.aadl2.AnnexLibrary;
import org.osate.aadl2.AnnexSubclause;
import org.osate.aadl2.DefaultAnnexLibrary;
import org.osate.aadl2.DefaultAnnexSubclause;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.modelsupport.errorreporting.AnalysisErrorReporterManager;
import org.osate.aadl2.modelsupport.errorreporting.AnalysisToParseErrorReporterAdapter;
import org.osate.aadl2.modelsupport.errorreporting.ParseErrorReporter;
import org.osate.aadl2.modelsupport.errorreporting.QueuingParseErrorReporter;
import org.osate.aadl2.modelsupport.errorreporting.QueuingParseErrorReporter.Message;
import org.osate.annexsupport.AnnexLinkingService;
import org.osate.annexsupport.AnnexLinkingServiceRegistry;
import org.osate.annexsupport.AnnexModel;
import org.osate.annexsupport.AnnexParser;
import org.osate.annexsupport.AnnexParserRegistry;
import org.osate.annexsupport.AnnexRegistry;
import org.osate.annexsupport.AnnexResolver;
import org.osate.annexsupport.AnnexResolverRegistry;
import org.osate.annexsupport.AnnexUtil;
import org.osate.annexsupport.AnnexValidator;
import org.osate.annexsupport.ParseResultHolder;
import org.osate.xtext.aadl2.Activator;
import antlr.RecognitionException;
public class AnnexParserAgent extends LazyLinker {
private static final boolean STANDALONE;
private static final AnnexParserRegistry PARSER_REGISTRY;
private static final AnnexResolverRegistry RESOLVER_REGISTRY;
private static final AnnexLinkingServiceRegistry LINKING_SERVICE_REGISTRY;
static {
boolean standalone = false;
AnnexParserRegistry parserRegistry = null;
AnnexResolverRegistry resolverRegistry = null;
AnnexLinkingServiceRegistry linkingServiceRegistry = null;
try {
parserRegistry = (AnnexParserRegistry) AnnexRegistry.getRegistry(AnnexRegistry.ANNEX_PARSER_EXT_ID);
resolverRegistry = (AnnexResolverRegistry) AnnexRegistry.getRegistry(AnnexRegistry.ANNEX_RESOLVER_EXT_ID);
linkingServiceRegistry = (AnnexLinkingServiceRegistry) AnnexRegistry
.getRegistry(AnnexRegistry.ANNEX_LINKINGSERVICE_EXT_ID);
} catch (NoClassDefFoundError e) {
// we're running without osgi
standalone = true;
}
STANDALONE = standalone;
PARSER_REGISTRY = parserRegistry;
RESOLVER_REGISTRY = resolverRegistry;
LINKING_SERVICE_REGISTRY = linkingServiceRegistry;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.xtext.linking.impl.AbstractCleaningLinker#afterModelLinked(org.eclipse.emf.ecore.EObject,
* org.eclipse.xtext.diagnostics.IDiagnosticConsumer)
*/
@Override
protected void afterModelLinked(EObject model, IDiagnosticConsumer diagnosticsConsumer) {
// we can't process annexes in standalone mode yet
if (STANDALONE) {
return;
}
String filename = model.eResource().getURI().lastSegment();
if (model instanceof AadlPackage) {
// do this only for packages
List<DefaultAnnexLibrary> all = AnnexUtil.getAllDefaultAnnexLibraries((AadlPackage) model);
for (DefaultAnnexLibrary defaultAnnexLibrary : all) {
ParserFunction<AnnexLibrary> parserFunction = AnnexParser::parseAnnexLibrary;
Consumer<AnnexLibrary> setParsedAnnexLibrary = defaultAnnexLibrary::setParsedAnnexLibrary;
Consumer<AnnexLibrary> copyModes = annexLibrary -> {
};
processAnnexSection(defaultAnnexLibrary, defaultAnnexLibrary.getSourceText(), filename,
diagnosticsConsumer, parserFunction, setParsedAnnexLibrary, copyModes);
}
}
// do this for both packages and property sets
List<DefaultAnnexSubclause> asl = AnnexUtil.getAllDefaultAnnexSubclauses(model);
for (DefaultAnnexSubclause defaultAnnexSubclause : asl) {
ParserFunction<AnnexSubclause> parserFunction = AnnexParser::parseAnnexSubclause;
Consumer<AnnexSubclause> setParsedAnnexSubclause = defaultAnnexSubclause::setParsedAnnexSubclause;
Consumer<AnnexSubclause> copyModes = annexSubclause -> annexSubclause.getInModes()
.addAll(defaultAnnexSubclause.getInModes());
processAnnexSection(defaultAnnexSubclause, defaultAnnexSubclause.getSourceText(), filename,
diagnosticsConsumer, parserFunction, setParsedAnnexSubclause, copyModes);
}
}
/**
* Used to indicate to
* {@link AnnexParserAgent#processAnnexSection(NamedElement, String, String, IDiagnosticConsumer, ParserFunction, Consumer, Consumer)}
* which of {@code parser}'s methods to call. Expected to be either
* {@link AnnexParser#parseAnnexLibrary(String, String, String, int, int, ParseErrorReporter)} or
* {@link AnnexParser#parseAnnexSubclause(String, String, String, int, int, ParseErrorReporter)}.
*
* @param <A> The annex section type, either {@link AnnexLibrary} or {@link AnnexSubclause}.
*/
@FunctionalInterface
private interface ParserFunction<A extends NamedElement> {
A parse(AnnexParser parser, String annexName, String source, String filename, int line, int offset,
ParseErrorReporter errReporter) throws RecognitionException;
}
/**
* Common functionality for processing either a {@link DefaultAnnexLibrary} or a {@link DefaultAnnexSubclause}.
* Processing involves parsing the text, attaching the resulting {@link AnnexLibrary} or {@link AnnexSubclause} to
* the {@link DefaultAnnexLibrary} or {@link DefaultAnnexSubclause}, setting the modes for the resulting
* {@link AnnexSubclause}, and either running the resolver or the linking service, depending upon which one if
* available. If the resolver produces errors, then the {@link AnnexLibrary} or {@link AnnexSubclause} will be
* detached from the {@link DefaultAnnexLibrary} or {@link DefaultAnnexSubclause}. All error, warning, and info
* messages that are produced from the parser, resolver, or linker will be passed along to
* {@code diagnosticsConsumer}.
*
* @param <A> Type of the resulting annex section. Expected to be {@link AnnexLibrary} or {@link AnnexSubclause}.
* @param <D> Type of the default annex section. Expected to be {@link DefaultAnnexLibrary} or
* {@link DefaultAnnexSubclause}.
* @param defaultAnnexSection Either the {@link DefaultAnnexLibrary} or {@link DefaultAnnexSubclause}.
* @param annexText Either the value of {@link DefaultAnnexLibrary#getSourceText()} or
* {@link DefaultAnnexSubclause#getSourceText()}.
* @param filename Name of the AADL file containing the annex section.
* @param diagnosticsConsumer Used for handling error, warning, and info messages.
* @param parserFunction Either
* {@link AnnexParser#parseAnnexLibrary(String, String, String, int, int, ParseErrorReporter)}
* or
* {@link AnnexParser#parseAnnexSubclause(String, String, String, int, int, ParseErrorReporter)}.
* @param setParsedAnnexSection Either {@link DefaultAnnexLibrary#setSourceText(String)} or
* {@link DefaultAnnexSubclause#setSourceText(String)}.
* @param copyModes Function for copying modes from the {@link DefaultAnnexSubclause} into the newly created
* {@link AnnexSubclause}. When processing an annex library, {@code copyModes} is expected to be a
* no-op {@link Consumer}.
*/
private <A extends NamedElement, D extends A> void processAnnexSection(D defaultAnnexSection, String annexText,
String filename, IDiagnosticConsumer diagnosticsConsumer, ParserFunction<A> parserFunction,
Consumer<A> setParsedAnnexSection, Consumer<A> copyModes) {
INode node = NodeModelUtils.findActualNodeFor(defaultAnnexSection);
int line = node.getStartLine() + computeLineOffset(node);
int offset = AnnexUtil.getAnnexOffset(defaultAnnexSection);
// look for plug-in parser
String annexName = defaultAnnexSection.getName();
if (annexText != null && annexText.length() > 6 && annexName != null) {
// strip {** **} from annex text
if (annexText.startsWith("{**")) {
annexText = annexText.substring(3, annexText.length() - 3);
}
annexName = AnnexModel.filterDisabledAnnexes(defaultAnnexSection, annexName);
AnnexParser ap = PARSER_REGISTRY.getAnnexParser(annexName);
try {
QueuingParseErrorReporter parseErrReporter = new QueuingParseErrorReporter();
parseErrReporter.setContextResource(defaultAnnexSection.eResource());
if (defaultAnnexSection instanceof AnnexSubclause) {
AnnexUtil.setCurrentAnnexSubclause((AnnexSubclause) defaultAnnexSection);
}
A annexSection = parserFunction.parse(ap, annexName, annexText, filename, line, offset,
parseErrReporter);
if (defaultAnnexSection instanceof AnnexSubclause) {
AnnexUtil.setCurrentAnnexSubclause(null);
}
if (ParseResultHolder.Factory.INSTANCE.adapt(defaultAnnexSection).getParseResult() == null) {
// Only consume messages for non-Xtext annexes
consumeMessages(parseErrReporter, diagnosticsConsumer, annexText, line, offset);
}
if (annexSection != null) {
annexSection.setName(annexName);
setParsedAnnexSection.accept(annexSection);
// copy in modes list
copyModes.accept(annexSection);
// now resolve reference so we get messages if we have references to undefined items
AnnexResolver resolver = RESOLVER_REGISTRY.getAnnexResolver(annexName);
AnnexLinkingService linkingService = LINKING_SERVICE_REGISTRY.getAnnexLinkingService(annexName);
if (resolver != null && parseErrReporter.getNumErrors() == 0) {// Don't resolve any annex with parsing errors.
QueuingParseErrorReporter resolveErrReporter = new QueuingParseErrorReporter();
AnalysisErrorReporterManager resolveErrManager = new AnalysisErrorReporterManager(
new AnalysisToParseErrorReporterAdapter.Factory(aadlRsrc -> resolveErrReporter));
resolver.resolveAnnex(annexName, Collections.singletonList(annexSection), resolveErrManager);
consumeMessages(resolveErrReporter, diagnosticsConsumer, annexText, line, offset);
if (resolveErrReporter.getNumErrors() != 0) {
AnnexValidator.setNoValidation(defaultAnnexSection, annexName);
}
} else if (linkingService != null) {
try {
XtextResource res = (XtextResource) defaultAnnexSection.eResource();
ILinker linker = res.getLinker();
linker.linkModel(annexSection, diagnosticsConsumer);
} catch (Exception e) {
String message = "Linking Service error in " + filename + " at line " + line;
IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, message, e);
Activator.getDefault().getLog().log(status);
}
}
}
if (parseErrReporter.getNumErrors() > 0) {
AnnexValidator.setNoValidation(defaultAnnexSection, annexName);
}
} catch (RecognitionException e) {
String message = "Major parsing error in " + filename + " at line " + line;
IStatus status = new Status(IStatus.ERROR, Activator.PLUGIN_ID, message, e);
Activator.getDefault().getLog().log(status);
}
}
}
// Compute the number of line between the token "annex" and the token "{**".
private int computeLineOffset(INode node) {
int result = 0;
boolean next = true;
char c;
int index = 0;
String text = node.getText();
// Trim the space or new line before the keyword "annex".
while (text.charAt(index++) != 'a' && index < text.length()) {
;
}
index += 4; // Complete the word "annex".
while (next && index < text.length()) {
c = text.charAt(index);
if (c == '\n') {
result++;
} else if (c == '{') {
next = false;
}
index++;
}
return result;
}
private static void consumeMessages(QueuingParseErrorReporter errReporter, IDiagnosticConsumer diagnosticsConsumer,
String annexText, int annexLine, int annexOffset) {
for (Message message : errReporter.getErrors()) {
int lineOffset = StringUtils.ordinalIndexOf(annexText, "\n", message.line - annexLine) + 1;
int endOfLine = annexText.indexOf('\n', lineOffset);
if (endOfLine == -1) {
endOfLine = annexText.length();
} else if (endOfLine > 0 && annexText.charAt(endOfLine - 1) == '\r') {
endOfLine--;
}
int diagnosticOffset = annexOffset + lineOffset;
int diagnosticLength = endOfLine - lineOffset;
Diagnostic diagnostic = new Diagnostic() {
@Override
public String getMessage() {
return message.message;
}
@Override
public String getLocation() {
return null;
}
@Override
public int getLine() {
return message.line;
}
@Override
public int getColumn() {
return 1;
}
@Override
public int getOffset() {
return diagnosticOffset;
}
@Override
public int getLength() {
return diagnosticLength;
}
@Override
public int getLineEnd() {
return 0;
}
@Override
public int getColumnEnd() {
return 0;
}
};
Severity severity;
if (QueuingParseErrorReporter.ERROR.equals(message.kind)) {
severity = Severity.ERROR;
} else if (QueuingParseErrorReporter.WARNING.equals(message.kind)) {
severity = Severity.WARNING;
} else if (QueuingParseErrorReporter.INFO.equals(message.kind)) {
severity = Severity.INFO;
} else {
severity = null;
}
diagnosticsConsumer.consume(diagnostic, severity);
}
}
}