Skip to main content
summaryrefslogtreecommitdiffstats
blob: 12bb74a5a422468c661809ed59d59aca758906ee (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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
/*******************************************************************************
 * Copyright (c) 2006, 2012 Oracle. 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:
 *     Oracle - initial API and implementation
 ******************************************************************************/
package org.eclipse.jpt.jpa.ui.internal.selection;

import org.eclipse.core.resources.IFile;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jpt.common.core.utility.TextRange;
import org.eclipse.jpt.common.ui.internal.PropertyAdapter;
import org.eclipse.jpt.common.ui.internal.jface.SelectionChangedAdapter;
import org.eclipse.jpt.common.ui.internal.swt.widgets.DisplayTools;
import org.eclipse.jpt.common.utility.internal.ObjectTools;
import org.eclipse.jpt.common.utility.internal.model.value.DoublePropertyValueModel;
import org.eclipse.jpt.common.utility.internal.model.value.SimplePropertyValueModel;
import org.eclipse.jpt.common.utility.internal.model.value.TransformationPropertyValueModel;
import org.eclipse.jpt.common.utility.internal.transformer.AbstractTransformer;
import org.eclipse.jpt.common.utility.model.event.PropertyChangeEvent;
import org.eclipse.jpt.common.utility.model.listener.PropertyChangeAdapter;
import org.eclipse.jpt.common.utility.model.listener.PropertyChangeListener;
import org.eclipse.jpt.common.utility.model.value.ModifiablePropertyValueModel;
import org.eclipse.jpt.common.utility.model.value.PropertyValueModel;
import org.eclipse.jpt.common.utility.transformer.Transformer;
import org.eclipse.jpt.jpa.core.JpaFile;
import org.eclipse.jpt.jpa.core.JpaStructureNode;
import org.eclipse.jpt.jpa.ui.JpaFileModel;
import org.eclipse.jpt.jpa.ui.internal.plugin.JptJpaUiPlugin;
import org.eclipse.jpt.jpa.ui.selection.JpaEditorManager;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.texteditor.ITextEditor;

/**
 * JPA editor manager for a {@link ITextEditor text editor}.
 * <p>
 * If the JPA selection model changes, set the text editor's selection
 * accordingly. If the {@link #textEditor text editor}'s selection changes,
 * forward the corresponding JPA selection to the {@link #jpaSelectionModel
 * JPA selection model}.
 */
class JpaTextEditorManager
	implements JpaEditorManager
{
	/**
	 * The manager's text editor
	 */
	private final ITextEditor textEditor;

	/**
	 * Listen for the {@link #textEditor text editor}'s input to change.
	 * This can happen if the text editor is re-usable.
	 */
	private final IPropertyListener textEditorInputListener = new TextEditorInputListener();

	/**
	 * Listen for the {@link #textEditor text editor}'s selection to change.
	 */
	private final ISelectionChangedListener textEditorSelectionListener = new TextEditorSelectionListener();

	private final ModifiablePropertyValueModel<IFile> fileModel = new SimplePropertyValueModel<IFile>();

	/**
	 * We use the JPA file to calculate the JPA selection.
	 * We update the JPA file model's file model whenever the text editor's
	 * file changes.
	 */
	private final PropertyValueModel<JpaFile> jpaFileModel;

	/**
	 * Listen for the model or ourselves (via the file model) to change the
	 * JPA file.
	 */
	private final PropertyChangeListener jpaFileListener = new JpaFileListener();

	/**
	 * We update the JPA selection model whenever the text editor's selection
	 * changes.
	 */
	private final ModifiablePropertyValueModel<JpaStructureNode> jpaSelectionModel = new SimplePropertyValueModel<JpaStructureNode>();

	/**
	 * Listen for other views to change the JPA selection.
	 */
	private final PropertyChangeListener jpaSelectionListener = new JpaSelectionListener();


	// ********** constructor **********

	/* CU private */ JpaTextEditorManager(ITextEditor textEditor) {
		super();
		this.textEditor = textEditor;
		this.textEditor.addPropertyListener(this.textEditorInputListener);
		IPostSelectionProvider selProvider = this.getTextEditorSelectionProvider();
		if (selProvider != null) {
			selProvider.addPostSelectionChangedListener(this.textEditorSelectionListener);
		}

		this.jpaFileModel = this.buildJpaFileModel();
		this.jpaFileModel.addPropertyChangeListener(PropertyValueModel.VALUE, this.jpaFileListener);

		this.jpaSelectionModel.addPropertyChangeListener(PropertyValueModel.VALUE, this.jpaSelectionListener);

		// this will trigger a new JPA file
		// which will trigger us to set the JPA selection
		this.setFileModel();
	}

	/**
	 * The <em>public</em> JPA selection model will not allow clients to set the
	 * JPA selection to <code>null</code>. Only the editor manager can set the
	 * selection to <code>null</code>. A client may set the JPA selection to
	 * <code>null</code> when it does not have appropriate input. It is OK
	 * for the <em>client's</em> JPA selection be <code>null</code>; but the
	 * editor manager's JPA selection is the <em>master</em> JPA selection for
	 * the workbench page/window and drives the JPA selection for potentially
	 * multiple clients. As a result, clients can only modify the editor
	 * manager's JPA selection to be some non-<code>null</code> value.
	 */

	// ********** JPA file **********

	public PropertyValueModel<JpaFile> getJpaFileModel() {
		return this.jpaFileModel;
	}

	private PropertyValueModel<JpaFile> buildJpaFileModel() {
		return new DoublePropertyValueModel<JpaFile>(this.buildJpaFileModelModel());
	}

	private PropertyValueModel<PropertyValueModel<JpaFile>> buildJpaFileModelModel() {
		return new TransformationPropertyValueModel<IFile, PropertyValueModel<JpaFile>>(this.fileModel, JPA_FILE_MODEL_TRANSFORMER);
	}

	private static final Transformer<IFile, PropertyValueModel<JpaFile>> JPA_FILE_MODEL_TRANSFORMER = new JpaFileModelTransformer();

	/* CU private */ static class JpaFileModelTransformer
		extends AbstractTransformer<IFile, PropertyValueModel<JpaFile>>
	{
		@Override
		protected PropertyValueModel<JpaFile> transform_(IFile file) {
			return (JpaFileModel) file.getAdapter(JpaFileModel.class);
		}
	}

	/* CU private */ class JpaFileListener
			extends PropertyChangeAdapter
	{
		@Override
		public void propertyChanged(PropertyChangeEvent event) {
			JpaTextEditorManager.this.jpaFileChanged();
		}
	}

	/**
	 * Dispatch to the UI thread.
	 */
	/* CU private */ void jpaFileChanged() {
		this.execute(new JpaFileChangedRunnable());
	}

	/* CU private */ class JpaFileChangedRunnable
			implements Runnable
	{
		public void run() {
			JpaTextEditorManager.this.jpaFileChanged_();
		}

		@Override
		public String toString() {
			return ObjectTools.toString(this);
		}
	}

	/**
	 * Pre-condition: executing on the UI thread.
	 */
	/* CU private */ void jpaFileChanged_() {
		this.jpaSelectionModel.setValue(this.getTextEditorJpaSelection());
	}


	// ********** JPA selection **********

	public ModifiablePropertyValueModel<JpaStructureNode> getJpaSelectionModel() {
		return this.jpaSelectionModel;
	}

	/* CU private */ class JpaSelectionListener
			extends PropertyChangeAdapter
	{
		@Override
		public void propertyChanged(PropertyChangeEvent event) {
			JpaTextEditorManager.this.setTextEditorJpaSelection((JpaStructureNode) event.getNewValue());
		}
	}

	/* CU private */ void setJpaSelection(ISelection selection) {
		this.jpaSelectionModel.setValue(this.getTextEditorJpaSelection(selection));
	}


	// ********** text editor selection **********

	/* CU private */ class TextEditorSelectionListener
			extends SelectionChangedAdapter
	{
		@Override
		public void selectionChanged(SelectionChangedEvent event) {
			//If the focus is in the JPA Details view we do not want to handle
			//TextEditor selection events. When in the Details View we are not
			//listening for java change events so our cached text ranges are not updated.
			//We do not want the selection to change while editing in the details view
			//or the details view can be changed out from under us.
			//AbstractJavaPersistentType.getStructureNode(int) will cause a
			//synchronizeWithJavaSource() to be run, and we don't want that to happen
			//when we have focus.
			if (JptJpaUiPlugin.instance().focusIsNotInDaliView()) {
				JpaTextEditorManager.this.setJpaSelection(event.getSelection());
			}
		}
	}

	/**
	 * Dispatch to the UI thread.
	 */
	/* CU private */ void setTextEditorJpaSelection(JpaStructureNode selection) {
		this.execute(new SetTextEditorSelectionRunnable(selection));
	}

	/* CU private */ class SetTextEditorSelectionRunnable
			implements Runnable
	{
		private final JpaStructureNode selection;

		SetTextEditorSelectionRunnable(JpaStructureNode selection) {
			super();
			this.selection = selection;
		}

		public void run() {
			JpaTextEditorManager.this.setTextEditorJpaSelection_(this.selection);
		}

		@Override
		public String toString() {
			return ObjectTools.toString(this, this.selection);
		}
	}

	/**
	 * Pre-condition: executing on the UI thread.
	 * If the new JPA selection is not <code>null</code> and it is different
	 * from the text editor's current JPA selection, modify the text editor's
	 * selection.
	 */
	/* CU private */ void setTextEditorJpaSelection_(JpaStructureNode selection) {
		if ((selection != null) && (selection != this.getTextEditorJpaSelection())) {
			this.setTextEditorSelection(selection.getSelectionTextRange());
		}
	}

	private void setTextEditorSelection(TextRange textRange) {
		if (textRange != null) {
			// no need for a disposed check...
			this.textEditor.selectAndReveal(textRange.getOffset(), textRange.getLength());
		}
	}

	/**
	 * Return the JPA selection corresponding to the text editor's current
	 * selection.
	 * Pre-condition: executing on the UI thread.
	 */
	private JpaStructureNode getTextEditorJpaSelection() {
		// the selection provider can be null if the text editor was disposed
		// before we get a chance to [asynchronously] handle the event
		// (or if the text editor does not have a *post* selection provider)
		IPostSelectionProvider selProvider = this.getTextEditorSelectionProvider();
		return (selProvider == null) ? null : this.getTextEditorJpaSelection(selProvider.getSelection());
	}

	private JpaStructureNode getTextEditorJpaSelection(ISelection selection) {
		return (selection instanceof ITextSelection) ? this.getTextEditorJpaSelection((ITextSelection) selection) : null;
	}

	private JpaStructureNode getTextEditorJpaSelection(ITextSelection selection) {
		JpaFile jpaFile = this.jpaFileModel.getValue();
		return (jpaFile == null) ? null : jpaFile.getStructureNode(selection.getOffset());
	}


	// ********** text editor input **********

	/* CU private */ class TextEditorInputListener
			extends PropertyAdapter
	{
		@Override
		public void propertyChanged(Object source, int propertyID) {
			if (propertyID == IEditorPart.PROP_INPUT) {
				JpaTextEditorManager.this.setFileModel();
			}
		}
	}


	// ********** misc **********

	public IEditorPart getEditor() {
		return this.textEditor;
	}

	/* CU private */ void setFileModel() {
		this.fileModel.setValue(this.getTextEditorFile());
	}

	private IFile getTextEditorFile() {
		IEditorInput input = this.textEditor.getEditorInput();
		return (input instanceof IFileEditorInput) ? ((IFileEditorInput) input).getFile() : null;
	}

	private IPostSelectionProvider getTextEditorSelectionProvider() {
		ISelectionProvider selProvider = this.textEditor.getSelectionProvider();
		return (selProvider instanceof IPostSelectionProvider) ? (IPostSelectionProvider) selProvider : null;
	}

	private void execute(Runnable runnable) {
		DisplayTools.execute(this.textEditor.getSite().getShell().getDisplay(), runnable);
	}

	public void dispose() {
		this.jpaFileModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.jpaFileListener);
		this.jpaSelectionModel.removePropertyChangeListener(PropertyValueModel.VALUE, this.jpaSelectionListener);
		IPostSelectionProvider selProvider = this.getTextEditorSelectionProvider();
		if (selProvider != null) {
			selProvider.removePostSelectionChangedListener(this.textEditorSelectionListener);
		}
		this.textEditor.removePropertyListener(this.textEditorInputListener);
	}

	@Override
	public String toString() {
		return ObjectTools.toString(this, this.textEditor);
	}
}

Back to the top