Skip to main content
summaryrefslogtreecommitdiffstats
blob: 15c6980b03b0c4aa47b18ab6b86f3a6884dabc08 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
/*******************************************************************************
 * Copyright (c) 2000, 2008 IBM Corporation and others.
 * 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
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.jface.text.link;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.eclipse.core.runtime.Assert;

import org.eclipse.jface.text.IDocument;


/**
 * A linked mode manager ensures exclusive access of linked position infrastructures to documents. There
 * is at most one <code>LinkedModeManager</code> installed on the same document. The <code>getManager</code>
 * methods will return the existing instance if any of the specified documents already have an installed
 * manager.
 *
 * @since 3.0
 */
class LinkedModeManager {

	/**
	 * Our implementation of <code>ILinkedModeListener</code>.
	 */
	private class Listener implements ILinkedModeListener {

		@Override
		public void left(LinkedModeModel model, int flags) {
			LinkedModeManager.this.left(model, flags);
		}

		@Override
		public void suspend(LinkedModeModel model) {
			// not interested
		}

		@Override
		public void resume(LinkedModeModel model, int flags) {
			// not interested
		}

	}

	/** Global map from documents to managers. */
	private static Map<IDocument, LinkedModeManager> fgManagers= new HashMap<>();

	/**
	 * Returns whether there exists a <code>LinkedModeManager</code> on <code>document</code>.
	 *
	 * @param document the document of interest
	 * @return <code>true</code> if there exists a <code>LinkedModeManager</code> on <code>document</code>, <code>false</code> otherwise
	 */
	public static boolean hasManager(IDocument document) {
		return fgManagers.get(document) != null;
	}

	/**
	 * Returns whether there exists a <code>LinkedModeManager</code> on any of the <code>documents</code>.
	 *
	 * @param documents the documents of interest
	 * @return <code>true</code> if there exists a <code>LinkedModeManager</code> on any of the <code>documents</code>, <code>false</code> otherwise
	 */
	public static boolean hasManager(IDocument[] documents) {
		for (IDocument document : documents) {
			if (hasManager(document))
				return true;
		}
		return false;
	}

	/**
	 * Returns the manager for the given documents. If <code>force</code> is
	 * <code>true</code>, any existing conflicting managers are canceled, otherwise,
	 * the method may return <code>null</code> if there are conflicts.
	 *
	 * @param documents the documents of interest
	 * @param force whether to kill any conflicting managers
	 * @return a manager able to cover the requested documents, or <code>null</code> if there is a conflict and <code>force</code> was set to <code>false</code>
	 */
	public static LinkedModeManager getLinkedManager(IDocument[] documents, boolean force) {
		if (documents == null || documents.length == 0)
			return null;

		Set<LinkedModeManager> mgrs= new HashSet<>();
		LinkedModeManager mgr= null;
		for (IDocument document : documents) {
			mgr= fgManagers.get(document);
			if (mgr != null)
				mgrs.add(mgr);
		}
		if (mgrs.size() > 1)
			if (force) {
				for (LinkedModeManager m : mgrs) {
					m.closeAllEnvironments();
				}
			} else {
				return null;
			}

		if (mgrs.size() == 0)
			mgr= new LinkedModeManager();

		for (IDocument document : documents)
			fgManagers.put(document, mgr);

		return mgr;
	}

	/**
	 * Cancels any linked mode manager for the specified document.
	 *
	 * @param document the document whose <code>LinkedModeManager</code> should be canceled
	 */
	public static void cancelManager(IDocument document) {
		LinkedModeManager mgr= fgManagers.get(document);
		if (mgr != null)
			mgr.closeAllEnvironments();
	}

	/** The hierarchy of environments managed by this manager. */
	private Stack<LinkedModeModel> fEnvironments= new Stack<>();
	private Listener fListener= new Listener();

	/**
	 * Notify the manager about a leaving model.
	 *
	 * @param model the model to nest
	 * @param flags the reason and commands for leaving linked mode
	 */
	private void left(LinkedModeModel model, int flags) {
		if (!fEnvironments.contains(model))
			return;

		while (!fEnvironments.isEmpty()) {
			LinkedModeModel env= fEnvironments.pop();
			if (env == model)
				break;
			env.exit(ILinkedModeListener.NONE);
		}

		if (fEnvironments.isEmpty()) {
			removeManager();
		}
	}

	private void closeAllEnvironments() {
		while (!fEnvironments.isEmpty()) {
			LinkedModeModel env= fEnvironments.pop();
			env.exit(ILinkedModeListener.NONE);
		}

		removeManager();
	}

	private void removeManager() {
		for (Iterator<LinkedModeManager> it= fgManagers.values().iterator(); it.hasNext();) {
			if (it.next() == this)
				it.remove();
		}
	}

    /**
     * Tries to nest the given <code>LinkedModeModel</code> onto the top of
     * the stack of environments managed by the receiver. If <code>force</code>
     * is <code>true</code>, any environments on the stack that create a conflict
     * are killed.
     *
     * @param model the model to nest
     * @param force whether to force the addition of the model
     * @return <code>true</code> if nesting was successful, <code>false</code> otherwise (only possible if <code>force</code> is <code>false</code>
     */
    public boolean nestEnvironment(LinkedModeModel model, boolean force) {
    	Assert.isNotNull(model);

    	try {
    		while (true) {
    			if (fEnvironments.isEmpty()) {
    				model.addLinkingListener(fListener);
    				fEnvironments.push(model);
    				return true;
    			}

    			LinkedModeModel top= fEnvironments.peek();
    			if (model.canNestInto(top)) {
    				model.addLinkingListener(fListener);
    				fEnvironments.push(model);
    				return true;
    			} else if (!force) {
    				return false;
    			} else { // force
    				fEnvironments.pop();
    				top.exit(ILinkedModeListener.NONE);
    				// continue;
    			}
    		}
    	} finally {
    		// if we remove any, make sure the new one got inserted
    		Assert.isTrue(fEnvironments.size() > 0);
    	}
    }

	/**
	 * Returns the <code>LinkedModeModel</code> that is on top of the stack of
	 * environments managed by the receiver.
	 *
	 * @return the topmost <code>LinkedModeModel</code>
	 */
	public LinkedModeModel getTopEnvironment() {
		if (fEnvironments.isEmpty())
			return null;
		return fEnvironments.peek();
	}
}

Back to the top