Skip to main content
summaryrefslogtreecommitdiffstats
blob: f8bb83f7e998cb57d34f4213480f64209201c348 (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
/****************************************************************************
 * Copyright (c) 2017, 2018 Remain Software
 * 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:
 *    Wim Jongman <wim.jongman@remainsoftware.com> - initial API and implementation
 *****************************************************************************/
package org.eclipse.tips.core.internal;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeSupport;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.eclipse.tips.core.ITipManager;
import org.eclipse.tips.core.TipProvider;

/**
 * An abstract implementation of ITipManager with additional control API. While
 * the rest of the framework must work with ITipManager, this class provides API
 * to open the dialog and do low level housekeeping that is of no concern to
 * external participants (Tip and TipProvider).
 *
 */
public abstract class TipManager implements ITipManager {

	private Map<String, TipProvider> fProviders = new HashMap<>();
	private Map<Integer, List<String>> fProviderPrio = new TreeMap<>();
	private boolean fOpen;
	private boolean fServeReadTips = false;
	private boolean fIsDiposed;
	private PropertyChangeSupport fChangeSupport = new PropertyChangeSupport(this);

	/**
	 * May start a dialog at startup.
	 */
	public static final int START_DIALOG = 0;

	/**
	 * May do background tasks but not show a dialog on startup.
	 */
	public static final int START_BACKGROUND = 1;

	/**
	 * Tips may only start on explicit user request.
	 */
	public static final int START_DISABLE = 2;

	/**
	 * Instantiates a new TipManager.
	 */
	public TipManager() {
	}

	/**
	 * Gets the provider with the specified ID.
	 *
	 * @param providerID the id of the provider to fetch
	 * @return the provider with the specified ID or null if no such provider
	 *         exists.
	 * @see TipProvider#getID()
	 */
	public TipProvider getProvider(String providerID) {
		checkDisposed();
		return fProviders.get(providerID);
	}

	/**
	 * Binds the passed provider to this manager. Implementations should override,
	 * call super, and then asynchronously call the
	 * {@link TipProvider#loadNewTips(org.eclipse.core.runtime.IProgressMonitor)}
	 * method.
	 *
	 * This manager then starts listening to the a {@link TipProvider#PROP_READY}
	 * property change event and resends it through its own change support.
	 *
	 * @param provider the {@link TipProvider} to register.
	 *
	 * @return this
	 */
	@Override
	public ITipManager register(TipProvider provider) {
		checkDisposed();
		String message = MessageFormat.format(Messages.TipManager_0, provider.getID(), provider.getDescription());
		log(LogUtil.info(message));
		provider.setManager(this);
		addToMaps(provider, Integer.valueOf(getPriority(provider)));
		provider.getChangeSupport().addPropertyChangeListener(event -> {
			if (event.getPropertyName().equals(TipProvider.PROP_READY)) {
				PropertyChangeEvent newEvent = new PropertyChangeEvent(this, event.getPropertyName(), null, provider);
				newEvent.setPropagationId(event.getPropagationId());
				getChangeSupport().firePropertyChange(newEvent);
			}
		});
		return this;
	}

	public PropertyChangeSupport getChangeSupport() {
		return fChangeSupport;
	}

	private void checkDisposed() {
		if (isDisposed()) {
			throw new RuntimeException(Messages.TipManager_2);
		}

	}

	/**
	 * Calculates the priority that this provider has in the Tips framework. The
	 * {@link TipProvider#getExpression()} was purposed to aid in the calculation of
	 * the priority.
	 *
	 * @param provider the provider
	 * @return the priority, lower is higher, never negative.
	 */
	public abstract int getPriority(TipProvider provider);

	private synchronized void addToMaps(TipProvider pProvider, Integer pPriorityHint) {
		removeFromMaps(pProvider);
		addToProviderMaps(pProvider, pPriorityHint);
		addToPriorityMap(pProvider, pPriorityHint);
	}

	private void addToPriorityMap(TipProvider provider, Integer priorityHint) {
		if (!fProviderPrio.get(priorityHint).contains(provider.getID())) {
			if (!fProviderPrio.get(priorityHint).contains(provider.getID())) {
				fProviderPrio.get(priorityHint).add(provider.getID());
			}
		}
	}

	private void addToProviderMaps(TipProvider provider, Integer priorityHint) {
		fProviders.put(provider.getID(), provider);
		if (fProviderPrio.get(priorityHint) == null) {
			fProviderPrio.put(priorityHint, new ArrayList<>());
		}
	}

	private void removeFromMaps(TipProvider provider) {
		if (fProviders.containsKey(provider.getID())) {
			for (Map.Entry<Integer, List<String>> entry : fProviderPrio.entrySet()) {
				entry.getValue().remove(provider.getID());
			}
			fProviders.remove(provider.getID());
		}
	}

	/**
	 * The returned list contains providers ready to serve tips and is guaranteed to
	 * be in a prioritised order according the implementation of this manager.
	 *
	 * @return the prioritised list of ready providers with tips in an immutable
	 *         list.
	 */
	public List<TipProvider> getProviders() {
		checkDisposed();
		if (fProviders == null) {
			return Collections.emptyList();
		}
		ArrayList<TipProvider> result = new ArrayList<>();
		for (Map.Entry<Integer, List<String>> entry : fProviderPrio.entrySet()) {
			for (String id : entry.getValue()) {
				if (fProviders.get(id).isReady()) {
					result.add(fProviders.get(id));
				}
			}
		}
		return Collections.unmodifiableList(result);
	}

	/**
	 * Determines if the Tips framework must run at startup. The default
	 * implementation returns {@link #START_DIALOG} , subclasses should probably
	 * override this if they want to give users a choice.
	 *
	 * @return Returns {@link #START_DIALOG}, {@link #START_BACKGROUND} or
	 *         {@link #START_DISABLE}.
	 * @see TipManager#setStartUpBehavior(int)
	 */
	public int getStartupBehavior() {
		checkDisposed();
		return START_DIALOG;
	}

	/**
	 * Determines what level of startup actions the Tips framework may do.
	 *
	 * @param startupBehavior Use {@link TipManager#START_DIALOG} to allow a dialog
	 *                        at startup and possibly query for new content,
	 *                        {@link #START_BACKGROUND} to query for new content but
	 *                        not show a dialog or {@link #START_DISABLE} to not do
	 *                        startup actions at all.
	 *
	 * @return this
	 *
	 * @see #isRunAtStartup()
	 */
	public abstract TipManager setStartupBehavior(int startupBehavior);

	/**
	 * The default implementation disposes of this manager and all the TipProviders
	 * when the dialog is disposed. Subclasses may override but must call super.
	 */
	public void dispose() {
		checkDisposed();
		try {
			for (TipProvider provider : fProviders.values()) {
				try {
					provider.dispose();
				} catch (Exception e) {
					log(LogUtil.error(e));
				}
			}
		} finally {
			fProviders.clear();
			fProviderPrio.clear();
			fIsDiposed = true;
		}
	}

	/**
	 * @return true if this manager is currently open.
	 */
	@Override
	public boolean isOpen() {
		// checkDisposed();
		if (isDisposed())
			return false;
		return fOpen;
	}

	protected void setOpen(boolean open) {
		fOpen = open;
	}

	/**
	 * Indicates whether read tips must be served or not. Subclasses could override,
	 * to save the state somewhere, but must call super.
	 *
	 * @param serveRead true of read tips may be served by the {@link TipProvider}s
	 * @return this
	 * @see TipManager#mustServeReadTips()
	 */
	public TipManager setServeReadTips(boolean serveRead) {
		checkDisposed();
		fServeReadTips = serveRead;
		return this;
	}

	/**
	 * Indicates whether already read tips must be served or not.
	 *
	 * @return true or false
	 * @see #setServeReadTips(boolean)
	 */
	@Override
	public boolean mustServeReadTips() {
		checkDisposed();
		return fServeReadTips;
	}

	public boolean isDisposed() {
		return fIsDiposed;
	}

	@Override
	public boolean hasContent() {
		for (TipProvider provider : getProviders()) {
			if (provider.isReady() && !provider.getTips().isEmpty()) {
				return true;
			}
		}
		return false;
	}
}

Back to the top