All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jface.text.link.LinkedModeModel Maven / Gradle / Ivy

Go to download

AspectJ tools most notably contains the AspectJ compiler (AJC). AJC applies aspects to Java classes during compilation, fully replacing Javac for plain Java classes and also compiling native AspectJ or annotation-based @AspectJ syntax. Furthermore, AJC can weave aspects into existing class files in a post-compile binary weaving step. This library is a superset of AspectJ weaver and hence also of AspectJ runtime.

There is a newer version: 1.9.22.1
Show newest version
/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jface.text.link;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.eclipse.core.runtime.Assert;

import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BadPositionCategoryException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension;
import org.eclipse.jface.text.IDocumentExtension.IReplace;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IPositionUpdater;
import org.eclipse.jface.text.Position;


/**
 * The model for linked mode, umbrellas several
 * {@link LinkedPositionGroup}s. Once installed, the model
 * propagates any changes to a position to all its siblings in the same position
 * group.
 * 

* Setting up a model consists of first adding * LinkedPositionGroups to it, and then installing the * model by either calling {@link #forceInstall()} or * {@link #tryInstall()}. After installing the model, it becomes * sealed and no more groups may be added. *

*

* If a document change occurs that would modify more than one position * group or that would invalidate the disjointness requirement of the positions, * the model is torn down and all positions are deleted. The same happens * upon calling {@link #exit(int)}. *

*

Nesting

*

* A LinkedModeModel may be nested into another model. This * happens when installing a model the positions of which all fit into a * single position in a parent model that has previously been installed on * the same document(s). *

*

* Clients may instantiate instances of this class. *

* * @since 3.0 * @noextend This class is not intended to be subclassed by clients. */ public class LinkedModeModel { /** * Checks whether there is already a model installed on document. * * @param document the IDocument of interest * @return true if there is an existing model, false * otherwise */ public static boolean hasInstalledModel(IDocument document) { // if there is a manager, there also is a model return LinkedModeManager.hasManager(document); } /** * Checks whether there is already a linked mode model installed on any of * the documents. * * @param documents the IDocuments of interest * @return true if there is an existing model, false * otherwise */ public static boolean hasInstalledModel(IDocument[] documents) { // if there is a manager, there also is a model return LinkedModeManager.hasManager(documents); } /** * Cancels any linked mode model on the specified document. If there is no * model, nothing happens. * * @param document the document whose LinkedModeModel should * be canceled */ public static void closeAllModels(IDocument document) { LinkedModeManager.cancelManager(document); } /** * Returns the model currently active on document at * offset, or null if there is none. * * @param document the document for which the caller asks for a * model * @param offset the offset into document, as there may be * several models on a document * @return the model currently active on document, or * null */ public static LinkedModeModel getModel(IDocument document, int offset) { if (!hasInstalledModel(document)) return null; LinkedModeManager mgr= LinkedModeManager.getLinkedManager(new IDocument[] {document}, false); if (mgr != null) return mgr.getTopEnvironment(); return null; } /** * Encapsulates the edition triggered by a change to a linking position. Can * be applied to a document as a whole. */ private class Replace implements IReplace { /** The edition to apply on a document. */ private TextEdit fEdit; /** * Creates a new instance. * * @param edit the edition to apply to a document. */ public Replace(TextEdit edit) { fEdit= edit; } @Override public void perform(IDocument document, IDocumentListener owner) throws RuntimeException, MalformedTreeException { document.removeDocumentListener(owner); fIsChanging= true; try { fEdit.apply(document, TextEdit.UPDATE_REGIONS | TextEdit.CREATE_UNDO); } catch (BadLocationException e) { /* XXX: perform should really throw a BadLocationException * see https://bugs.eclipse.org/bugs/show_bug.cgi?id=52950 */ throw new RuntimeException(e); } finally { document.addDocumentListener(owner); fIsChanging= false; } } } /** * The document listener triggering the linked updating of positions * managed by this model. */ private class DocumentListener implements IDocumentListener { private boolean fExit= false; /** * Checks whether event occurs within any of the positions * managed by this model. If not, the linked mode is left. * * @param event {@inheritDoc} */ @Override public void documentAboutToBeChanged(DocumentEvent event) { // don't react on changes executed by the parent model if (fParentEnvironment != null && fParentEnvironment.isChanging()) return; for (LinkedPositionGroup group : fGroups) { if (!group.isLegalEvent(event)) { fExit= true; return; } } } /** * Propagates a change to a linked position to all its sibling positions. * * @param event {@inheritDoc} */ @Override public void documentChanged(DocumentEvent event) { if (fExit) { LinkedModeModel.this.exit(ILinkedModeListener.EXTERNAL_MODIFICATION); return; } fExit= false; // don't react on changes executed by the parent model if (fParentEnvironment != null && fParentEnvironment.isChanging()) return; // collect all results Map result= null; for (LinkedPositionGroup group : fGroups) { Map map= group.handleEvent(event); if (result != null && map != null) { // exit if more than one position was changed LinkedModeModel.this.exit(ILinkedModeListener.EXTERNAL_MODIFICATION); return; } if (map != null) result= map; } if (result != null) { // edit all documents for (Entry entry : result.entrySet()) { IDocument doc = entry.getKey(); TextEdit edit= entry.getValue(); Replace replace= new Replace(edit); // apply the edition, either as post notification replace // on the calling document or directly on any other // document if (doc == event.getDocument()) { if (doc instanceof IDocumentExtension) { ((IDocumentExtension) doc).registerPostNotificationReplace(this, replace); } else { // ignore - there is no way we can log from JFace text... } } else { replace.perform(doc, this); } } } } } /** The set of linked position groups. */ private final List fGroups= new ArrayList<>(); /** The set of documents spanned by this group. */ private final Set fDocuments= new HashSet<>(); /** The position updater for linked positions. */ private final IPositionUpdater fUpdater= new InclusivePositionUpdater(getCategory()); /** The document listener on the documents affected by this model. */ private final DocumentListener fDocumentListener= new DocumentListener(); /** The parent model for a hierarchical set up, or null. */ private LinkedModeModel fParentEnvironment; /** * The position in fParentEnvironment that includes all * positions in this object, or null if there is no parent * model. */ private LinkedPosition fParentPosition= null; /** * A model is sealed once it has children - no more positions can be * added. */ private boolean fIsSealed= false; /** true when this model is changing documents. */ private boolean fIsChanging= false; /** The linked listeners. */ private final List fListeners= new ArrayList<>(); /** Flag telling whether we have exited: */ private boolean fIsActive= true; /** * The sequence of document positions as we are going to iterate through * them. */ private List fPositionSequence= new ArrayList<>(); /** * Whether we are in the process of editing documents (set by Replace, * read by DocumentListener. * * @return true if we are in the process of editing a * document, false otherwise */ private boolean isChanging() { return fIsChanging || fParentEnvironment != null && fParentEnvironment.isChanging(); } /** * Throws a BadLocationException if group * conflicts with this model's groups. * * @param group the group being checked * @throws BadLocationException if group conflicts with this * model's groups */ private void enforceDisjoint(LinkedPositionGroup group) throws BadLocationException { for (LinkedPositionGroup g : fGroups) { g.enforceDisjoint(group); } } /** * Causes this model to exit. Called either if an illegal document change * is detected, or by the UI. * * @param flags the exit flags as defined in {@link ILinkedModeListener} */ public void exit(int flags) { if (!fIsActive) return; fIsActive= false; for (IDocument doc : fDocuments) { try { doc.removePositionCategory(getCategory()); } catch (BadPositionCategoryException e) { // won't happen Assert.isTrue(false); } doc.removePositionUpdater(fUpdater); doc.removeDocumentListener(fDocumentListener); } fDocuments.clear(); fGroups.clear(); List listeners= new ArrayList<>(fListeners); fListeners.clear(); for (ILinkedModeListener listener : listeners) { listener.left(this, flags); } if (fParentEnvironment != null) fParentEnvironment.resume(flags); } /** * Causes this model to stop forwarding updates. The positions are not * unregistered however, which will only happen when exit * is called, or after the next document change. * * @param flags the exit flags as defined in {@link ILinkedModeListener} * @since 3.1 */ public void stopForwarding(int flags) { fDocumentListener.fExit= true; } /** * Puts document into the set of managed documents. This * involves registering the document listener and adding our position * category. * * @param document the new document */ private void manageDocument(IDocument document) { if (!fDocuments.contains(document)) { fDocuments.add(document); document.addPositionCategory(getCategory()); document.addPositionUpdater(fUpdater); document.addDocumentListener(fDocumentListener); } } /** * Returns the position category used by this model. * * @return the position category used by this model */ private String getCategory() { return toString(); } /** * Adds a position group to this LinkedModeModel. This * method may not be called if the model has been installed. Also, if * a UI has been set up for this model, it may not pick up groups * added afterwards. *

* If the positions in group conflict with any other group in * this model, a BadLocationException is thrown. Also, * if this model is nested inside another one, all positions in all * groups of the child model have to reside within a single position in the * parent model, otherwise a BadLocationException is thrown. *

*

* If group already exists, nothing happens. *

* * @param group the group to be added to this model * @throws BadLocationException if the group conflicts with the other groups * in this model or violates the nesting requirements. * @throws IllegalStateException if the method is called when the * model is already sealed */ public void addGroup(LinkedPositionGroup group) throws BadLocationException { if (group == null) throw new IllegalArgumentException("group may not be null"); //$NON-NLS-1$ if (fIsSealed) throw new IllegalStateException("model is already installed"); //$NON-NLS-1$ if (fGroups.contains(group)) // nothing happens return; enforceDisjoint(group); group.seal(); fGroups.add(group); } /** * Creates a new model. * @since 3.1 */ public LinkedModeModel() { } /** * Installs this model, which includes registering as document * listener on all involved documents and storing global information about * this model. Any conflicting model already present will be * closed. *

* If an exception is thrown, the installation failed and * the model is unusable. *

* * @throws BadLocationException if some of the positions of this model * were not valid positions on their respective documents */ public void forceInstall() throws BadLocationException { if (!install(true)) Assert.isTrue(false); } /** * Installs this model, which includes registering as document * listener on all involved documents and storing global information about * this model. If there is another model installed on the * document(s) targeted by the receiver that conflicts with it, installation * may fail. *

* The return value states whether installation was * successful; if not, the model is not installed and will not work. *

* * @return true if installation was successful, * false otherwise * @throws BadLocationException if some of the positions of this model * were not valid positions on their respective documents */ public boolean tryInstall() throws BadLocationException { return install(false); } /** * Installs this model, which includes registering as document * listener on all involved documents and storing global information about * this model. The return value states whether installation was * successful; if not, the model is not installed and will not work. * The return value can only then become false if * force was set to false as well. * * @param force if true, any other model that cannot * coexist with this one is canceled; if false, * install will fail when conflicts occur and return false * @return true if installation was successful, * false otherwise * @throws BadLocationException if some of the positions of this model * were not valid positions on their respective documents */ private boolean install(boolean force) throws BadLocationException { if (fIsSealed) throw new IllegalStateException("model is already installed"); //$NON-NLS-1$ enforceNotEmpty(); IDocument[] documents= getDocuments(); LinkedModeManager manager= LinkedModeManager.getLinkedManager(documents, force); // if we force creation, we require a valid manager Assert.isTrue(!(force && manager == null)); if (manager == null) return false; if (!manager.nestEnvironment(this, force)) if (force) Assert.isTrue(false); else return false; // we set up successfully. After this point, exit has to be called to // remove registered listeners... fIsSealed= true; if (fParentEnvironment != null) fParentEnvironment.suspend(); // register positions try { for (LinkedPositionGroup group : fGroups) { group.register(this); } return true; } catch (BadLocationException e){ // if we fail to add, make sure to release all listeners again exit(ILinkedModeListener.NONE); throw e; } } /** * Asserts that there is at least one linked position in this linked mode * model, throws an IllegalStateException otherwise. */ private void enforceNotEmpty() { boolean hasPosition= false; for (LinkedPositionGroup linkedPositionGroup : fGroups) if (!linkedPositionGroup.isEmpty()) { hasPosition= true; break; } if (!hasPosition) throw new IllegalStateException("must specify at least one linked position"); //$NON-NLS-1$ } /** * Collects all the documents that contained positions are set upon. * @return the set of documents affected by this model */ private IDocument[] getDocuments() { Set docs= new HashSet<>(); for (LinkedPositionGroup group : fGroups) { docs.addAll(Arrays.asList(group.getDocuments())); } return docs.toArray(new IDocument[docs.size()]); } /** * Returns whether the receiver can be nested into the given parent * model. If yes, the parent model and its position that the receiver * fits in are remembered. * * @param parent the parent model candidate * @return true if the receiver can be nested into parent, false otherwise */ boolean canNestInto(LinkedModeModel parent) { for (LinkedPositionGroup group : fGroups) { if (!enforceNestability(group, parent)) { fParentPosition= null; return false; } } Assert.isNotNull(fParentPosition); fParentEnvironment= parent; return true; } /** * Called by nested models when a group is added to them. All * positions in all groups of a nested model have to fit inside a * single position in the parent model. * * @param group the group of the nested model to be adopted. * @param model the model to check against * @return false if it failed to enforce nestability */ private boolean enforceNestability(LinkedPositionGroup group, LinkedModeModel model) { Assert.isNotNull(model); Assert.isNotNull(group); try { for (LinkedPositionGroup pg : model.fGroups) { LinkedPosition pos; pos= pg.adopt(group); if (pos != null && fParentPosition != null && fParentPosition != pos) return false; // group does not fit into one parent position, which is illegal else if (fParentPosition == null && pos != null) fParentPosition= pos; } } catch (BadLocationException e) { return false; } // group must fit into exactly one of the parent's positions return fParentPosition != null; } /** * Returns whether this model is nested. * *

* This method is part of the private protocol between * LinkedModeUI and LinkedModeModel. *

* * @return true if this model is nested, * false otherwise */ public boolean isNested() { return fParentEnvironment != null; } /** * Returns the positions in this model that have a tab stop, in the * order they were added. * *

* This method is part of the private protocol between * LinkedModeUI and LinkedModeModel. *

* * @return the positions in this model that have a tab stop, in the * order they were added */ public List getTabStopSequence() { return fPositionSequence; } /** * Adds listener to the set of listeners that are informed * upon state changes. * * @param listener the new listener */ public void addLinkingListener(ILinkedModeListener listener) { Assert.isNotNull(listener); if (!fListeners.contains(listener)) fListeners.add(listener); } /** * Removes listener from the set of listeners that are * informed upon state changes. * * @param listener the new listener */ public void removeLinkingListener(ILinkedModeListener listener) { fListeners.remove(listener); } /** * Finds the position in this model that is closest after * toFind. toFind needs not be a position in * this model and serves merely as an offset. * *

* This method part of the private protocol between * LinkedModeUI and LinkedModeModel. *

* * @param toFind the position to search from * @return the closest position in the same document as toFind * after the offset of toFind, or null */ public LinkedPosition findPosition(LinkedPosition toFind) { LinkedPosition position= null; for (LinkedPositionGroup group : fGroups) { position= group.getPosition(toFind); if (position != null) break; } return position; } /** * Registers a LinkedPosition with this model. Called * by PositionGroup. * * @param position the position to register * @throws BadLocationException if the position cannot be added to its * document */ void register(LinkedPosition position) throws BadLocationException { Assert.isNotNull(position); IDocument document= position.getDocument(); manageDocument(document); try { document.addPosition(getCategory(), position); } catch (BadPositionCategoryException e) { // won't happen as the category has been added by manageDocument() Assert.isTrue(false); } int seqNr= position.getSequenceNumber(); if (seqNr != LinkedPositionGroup.NO_STOP) { fPositionSequence.add(position); } } /** * Suspends this model. */ private void suspend() { List l= new ArrayList<>(fListeners); for (ILinkedModeListener listener : l) { listener.suspend(this); } } /** * Resumes this model. flags can be NONE * or SELECT. * * @param flags NONE or SELECT */ private void resume(int flags) { List l= new ArrayList<>(fListeners); for (ILinkedModeListener listener : l) { listener.resume(this, flags); } } /** * Returns whether an offset is contained by any position in this * model. * * @param offset the offset to check * @return true if offset is included by any * position (see {@link LinkedPosition#includes(int)}) in this * model, false otherwise */ public boolean anyPositionContains(int offset) { for (LinkedPositionGroup group : fGroups) { if (group.contains(offset)) // take the first hit - exclusion is guaranteed by enforcing // disjointness when adding positions return true; } return false; } /** * Returns the linked position group that contains position, * or null if position is not contained in any * group within this model. Group containment is tested by calling * group.contains(position) for every group in * this model. * *

* This method part of the private protocol between * LinkedModeUI and LinkedModeModel. *

* * @param position the position the group of which is requested * @return the first group in this model for which * group.contains(position) returns true, * or null if no group contains position */ public LinkedPositionGroup getGroupForPosition(Position position) { for (LinkedPositionGroup group : fGroups) { if (group.contains(position)) return group; } return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy