OpenAadlResources.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.ge.internal.ui.xtext;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.model.IXtextModelListener;
import org.osate.aadl2.NamedElement;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Streams;
/**
* Data structure for tracking open AADL resources and listening for changes.
* If multiple Xtext editors are open for a single AADL resource, it is treated as a single open document.
* This structure actually tracks all xtext resources, not just AADL files.
*/
class OpenAadlResources {
private final Map<IXtextDocument, OpenAadlResource> documentToOpenResourceMap = new HashMap<>();
// Qualified names may not be unique. Qualified names will be stored in lowercase format
// OpenAadlResource values are considered newer.
private final ListMultimap<String, OpenAadlResource> qualifiedNameToOpenResourcesMap = ArrayListMultimap.create();
private final Map<IXtextDocument, IXtextModelListener> documentToInternalModelListenerMap = new HashMap<>();
private final List<XtextDocumentChangeListener> documentListeners = new CopyOnWriteArrayList<>();
private static class OpenAadlResource {
public final IXtextDocument document; // Linked list of documents for the resource. More recently updated documents will be sorted before other
// documents
public URI uri;
public String rootQualifiedName; // May be null. Must be lowercase
public OpenAadlResource(final URI uri, final IXtextDocument document) {
this.uri = uri;
this.document = document;
}
}
/**
* Returns the Xtext document for the specified root element.
* @param qualifiedName the qualified name of the root element
* @param resourceUri the resource URI of the resource containing the element
* @return the last document updated for the qualified name and resource or null if one does not exist
*/
public IXtextDocument getDocument(String qualifiedName, final URI resourceUri) {
if (qualifiedName == null || resourceUri == null) {
return null;
}
qualifiedName = qualifiedName.toLowerCase();
final Optional<OpenAadlResource> openResource = Streams.findLast(qualifiedNameToOpenResourcesMap.get(qualifiedName).stream()
.filter(r -> resourceUri.equals(r.uri)));
return openResource.map(r -> r.document).orElse(null);
}
/**
* Called to notify the instance that an Xtext document has been opened or it input has changed
* @param document the Xtext document from the editor which was opened.
* @param resourceUri the URI of the resource being edited by the editor which owns the document
*/
public void onXtextDocumentOpened(final IXtextDocument document, final URI resourceUri) {
removeEntriesForDocument(document);
// Create a new open resource object
final OpenAadlResource openAadlResource = new OpenAadlResource(resourceUri, document);
// Add the IXtextDocument -> OpenAadlResource mapping
documentToOpenResourceMap.put(document, openAadlResource);
// Create a new model listener
final IXtextModelListener newModelListener = createModelListener(document);
documentToInternalModelListenerMap.put(document, newModelListener);
document.addModelListener(newModelListener);
// Trigger the initial model changed event.
document.readOnly(resource -> {
newModelListener.modelChanged(resource);
return null;
});
}
/**
* Removes listeners and mappings related to the specified Xtext document
* @param document the document for which to remove listeners and mappings
* @return the previously open resource if any.
*/
private OpenAadlResource removeEntriesForDocument(final IXtextDocument document) {
// Remove the document if it was previously opened
final OpenAadlResource prevOpenResource = documentToOpenResourceMap.remove(document);
if (prevOpenResource != null) {
removeQualifiedNameMapping(prevOpenResource);
}
documentToInternalModelListenerMap.remove(document);
// Remove the model listener
final IXtextModelListener modelListener = documentToInternalModelListenerMap.get(document);
if (modelListener != null) {
document.removeModelListener(modelListener);
documentToInternalModelListenerMap.remove(document);
}
return prevOpenResource;
}
/**
* Called to notify the instance that an Xtext document has been closed
* @param document the document that has been closed
*/
public void onXtextDocumentClosed(final IXtextDocument document) {
final OpenAadlResource prevOpenResource = removeEntriesForDocument(document);
if (prevOpenResource != null) {
// Notify listeners of one last change since the resource is no longer open
for (final XtextDocumentChangeListener l : documentListeners) {
l.documentChanged(prevOpenResource.uri);
}
}
}
private IXtextModelListener createModelListener(final IXtextDocument document) {
return resource -> {
final OpenAadlResource openResource = documentToOpenResourceMap.get(document);
if(openResource != null) {
openResource.uri = resource.getURI();
// Determine the new qualified name
String newQualifiedName = null;
if (resource != null && resource.getContents().size() > 0) {
final EObject obj = resource.getContents().get(0);
if(obj instanceof NamedElement) {
final String elementQualifiedName = ((NamedElement)obj).getQualifiedName();
newQualifiedName = elementQualifiedName == null ? null : elementQualifiedName.toLowerCase();
}
}
// If the qualified name has changed, update qualified name and mapping
if((openResource.rootQualifiedName == null && newQualifiedName != null) ||
(openResource.rootQualifiedName != null && !openResource.rootQualifiedName.equals(newQualifiedName))) {
// Remove old name from mapping
removeQualifiedNameMapping(openResource);
// Update the qualified name
openResource.rootQualifiedName = newQualifiedName;
// Add new name to mapping to the end of the list. Entries on the end of the list are used first by getDocument()
if(newQualifiedName != null) {
qualifiedNameToOpenResourcesMap.put(newQualifiedName, openResource);
}
}
// Notify others
for (final XtextDocumentChangeListener l : documentListeners) {
l.documentChanged(resource.getURI());
}
}
};
}
/**
* Adds a listener which is notified when an Xtext document changes
* @param listener the listener to add
* @see #removeDocumentListener(XtextDocumentChangeListener)
*/
public void addDocumentListener(final XtextDocumentChangeListener listener) {
documentListeners.add(listener);
}
/**
* Removes a listener previously registered by {@link #addDocumentListener(XtextDocumentChangeListener)}
* @param listener the listener to remove
* @see #addDocumentListener(XtextDocumentChangeListener)
*/
public void removeDocumentListener(final XtextDocumentChangeListener listener) {
documentListeners.remove(listener);
}
private void removeQualifiedNameMapping(final OpenAadlResource openResource) {
if (openResource.rootQualifiedName != null) {
qualifiedNameToOpenResourcesMap.remove(openResource.rootQualifiedName, openResource);
}
}
/**
* Method for troubleshooting issues with the mappings
*/
@SuppressWarnings("unused")
private void dumpStats() {
System.out.println("Document to Open Resource Mappings: " + documentToOpenResourceMap.size());
System.out.println("Qualified Names to Open Resources Mappings: " + qualifiedNameToOpenResourcesMap.size());
System.out.println("Document to Model Listener Mappings: " + documentToInternalModelListenerMap.size());
}
}