diff options
Diffstat (limited to 'org.eclipse.tips.core/src/org/eclipse/tips/core/TipProvider.java')
-rw-r--r-- | org.eclipse.tips.core/src/org/eclipse/tips/core/TipProvider.java | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/org.eclipse.tips.core/src/org/eclipse/tips/core/TipProvider.java b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipProvider.java new file mode 100644 index 000000000..fde1da7b3 --- /dev/null +++ b/org.eclipse.tips.core/src/org/eclipse/tips/core/TipProvider.java @@ -0,0 +1,332 @@ +/**************************************************************************** + * 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; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.tips.core.internal.FinalTip; + +/** + * Class to provide tips to the tip framework. It is the job of this provider to + * manage its tips. Examples of managing tips are: + * + * <ul> + * <li>Loading tips from the internet</li> + * <li>Serve next, previous and current tip on request</li> + * </ul> + * + * After the TipProvider is instantiated by the {@link ITipManager}, the + * TipManager will insert itself by calling {@link #setManager(ITipManager)}. + * Then the TipManager will asynchronous call this providers' + * {@link #loadNewTips(IProgressMonitor)} method. The job of the load() method + * is to do long work like fetching new tips from the internet and storing them + * locally. There is no defined method on how tips should be stored locally, + * implementers are free to do what is needed. + * + * The constructor must return fast, meaning that tips may not be fetched from + * the Internet in the constructor. This should be done in the + * {@link #loadNewTips(IProgressMonitor)} method. + * + * To indicate that this provider is ready to serve tips, it should call the + * {@link #setTips(List)} method which then sets its <code>ready</code> flag. + * + */ +public abstract class TipProvider { + + private ITipManager fTipManager; + private int fTipIndex; + protected List<Tip> fTips = new ArrayList<>(); + private Tip fCurrentTip; + private boolean fReady; + private TipProviderListenerManager fListenerManager = new TipProviderListenerManager(); + private Tip fFinalTip = new FinalTip(getID()); + private String fExpression; + + /** + * The zero argument constructor must be able to instantiate the TipProvider. + * This method may also be used to quickly set the available tips by calling the + * {@link #setTips(List)} method. The constructor may not be used to load tips + * from the internet. Use the {@link #loadNewTips(IProgressMonitor)} method for + * this purpose. + * + * @see #loadNewTips(IProgressMonitor) + * @see #setTips(List) + */ + public TipProvider() { + } + + /** + * Provides the opportunity to release all held resources. + */ + public abstract void dispose(); + + /** + * @return the short description of this provider. + */ + public abstract String getDescription(); + + /** + * @return the ID of this provider + */ + public abstract String getID(); + + /** + * The image used by the UI for low resolution + * + * @return a 48x48 {@link TipImage} + */ + public abstract TipImage getImage(); + + /** + * Get a list of tips. The default implementation returns tips based on the + * following conditions: <br> + * <dl> + * <dt><code>pFilter</code> is false</dt> + * <dd>Return all read and unread tips.</dd> + * <dt><code>pFilter</code> is true</dt> + * <dd>Return read and unread tips if the tipManager may serve unread tips, + * otherwise return only unread tips.</dd> + * </dl> + * <p> + * Subclasses may override (calling super(false) to fetch the list) if they want + * to serve or sort the list of tips in a different way. + * + * @param filter + * false or true, see description above. + * @return an unmodifiable list of tips. + */ + public synchronized List<Tip> getTips(boolean filter) { + if (filter) { + return Collections.unmodifiableList(fTips // + .stream() // + .filter(tip -> getManager().mustServeReadTips() || !getManager().isRead(tip)) // + .sorted(Comparator.comparing(Tip::getCreationDate).reversed()) // + .collect(Collectors.toList())); + } + return Collections.unmodifiableList(fTips); + } + + /** + * @return the {@link Tip} that was last returned by {@link #getNextTip()} or + * {@link #getPreviousTip()} + */ + public synchronized Tip getCurrentTip() { + if (fCurrentTip == null) { + return getNextTip(); + } + return fCurrentTip; + } + + /** + * The next {@link Tip} is returned based on the read status of the Tip and the + * fact if already read tips must be served or not which is known by the + * {@link ITipManager}: ({@link ITipManager#mustServeReadTips()}). + * + * @return the next {@link Tip} + * @see #getPreviousTip() + * @see #getCurrentTip() + */ + public synchronized Tip getNextTip() { + boolean unreadOnly = !getManager().mustServeReadTips(); + List<Tip> list = getTips(unreadOnly); + if (list.isEmpty()) { + return setCurrentTip(fFinalTip); + } + if (!unreadOnly && fCurrentTip != null) { + fTipIndex++; + } else if (fCurrentTip != null && getManager().isRead(fCurrentTip)) { + fTipIndex++; + } + if (fTipIndex >= list.size()) { + fTipIndex = 0; + } + return setCurrentTip(list.get(fTipIndex)); + } + + /** + * @return the previous {@link Tip} + * @see #getNextTip() + * @see #getCurrentTip() + */ + public Tip getPreviousTip() { + List<Tip> list = getTips(!getManager().mustServeReadTips()); + if (list.isEmpty()) { + return setCurrentTip(fFinalTip); + } + fTipIndex--; + if (fTipIndex < 0) { + fTipIndex = list.size() - 1; + } + return setCurrentTip(list.get(fTipIndex)); + } + + /** + * @return the {@link ITipManager} of this provider, never null. + */ + public synchronized ITipManager getManager() { + return fTipManager; + } + + /** + * @return true if the provider is ready to deliver tips + */ + public final boolean isReady() { + return fReady; + } + + /** + * Is called asynchronously during startup of the TipManager to gather new tips. + * + * The provider is not available to the UI unless it has called it's + * {@link #setTips(List)} method. It is therefore possible that the provider is + * not immediately visible in the tip UI but will be added later. + * <p> + * If you run out of tips and you feel that you should load more tips on your + * own then you can also asynchronously call this method. A good place would be + * to override {@link #getTips(boolean)}, check if the supply of tips is + * sufficient and then call this method asynchronously. + * <p> + * One strategy is to do a long running fetch in this method and then store the + * tips locally. On the next run of the TipManager, the fetched tips can be + * served from the constructor (i.e. by calling {@link #setTips(List)}), making + * them available immediately + * + * @param monitor + * The monitor to report back progress. + * @return the status in case you want to report problems. + * @see TipProvider#setTips(List) + * @see TipProvider#isReady() + */ + public abstract IStatus loadNewTips(IProgressMonitor monitor); + + private synchronized Tip setCurrentTip(Tip pTip) { + fCurrentTip = pTip; + return fCurrentTip; + } + + /** + * Sets the TipManager. You should probably not call this method directly. This + * method is normally called after the provider is instantiated by the + * {@link ITipManager}. If you create the provider yourself you should register + * the provider with {@link ITipManager#register(TipProvider)} which in turn + * will call this method. Subclasses may override but must not forget to call + * super in order to save the {@link ITipManager}. + * + * @param tipManager + * the {@link ITipManager} + * @return this + */ + public synchronized TipProvider setManager(ITipManager tipManager) { + fTipManager = tipManager; + return this; + } + + /** + * Sets the tips for this provider, replacing the current set of tips, and sets + * the <code>ready</code> flag to true. This method is typically called from the + * constructor of the {@link TipProvider} but may also be called from the + * asynchronous {@link #loadNewTips(IProgressMonitor)} method. + * + * @param tips + * a list of {@link Tip} objects + * @return this + * @see #addTips(List) + * @see #isReady() + * @see #loadNewTips(IProgressMonitor) + */ + public TipProvider setTips(List<Tip> tips) { + if(getManager().isDisposed()) { + return this; + } + doSetTips(tips, true); + fReady = true; + fListenerManager.notifyListeners(TipProviderListener.EVENT_READY, this); + return this; + } + + /** + * Adds the passed tips to the set of tips this provider already has sets the + * <code>ready</code> flag to true. This method is typically called from the + * constructor of the {@link TipProvider} but may also be called from the + * asynchronous {@link #loadNewTips(IProgressMonitor)} method. + * + * @param tips + * a list of {@link Tip} objects + * @return this + * @see #setTips(List) + * @see #isReady() + * @see #loadNewTips(IProgressMonitor) + */ + public TipProvider addTips(List<Tip> tips) { + doSetTips(tips, false); + fReady = true; + fListenerManager.notifyListeners(TipProviderListener.EVENT_READY, this); + return this; + } + + private synchronized void doSetTips(List<Tip> tips, boolean replace) { + if (replace) { + fTips.clear(); + } + fTips.addAll(tips); + } + + /** + * Gets the listener manager so that interested parties can subscribe to the + * events of this provider. + * + * @return the {@link TipProviderListenerManager} + */ + public TipProviderListenerManager getListenerManager() { + return fListenerManager; + } + + /** + * Returns an expression that is used by the {@link ITipManager} to determine + * the priority of this provider. The expression can be used to advice the + * TipManager when the tips of this provider deserve priority. The Eclipse IDE + * TipManager uses the core expression from the o.e.core.runtime bundle. + * Example: The expression + * + * <pre> + * <with + * variable="activeWorkbenchWindow.activePerspective"> + * <equals value="org.eclipse.jdt.ui.JavaPerspective"></equals> + * </with> + * </pre> + * + * will give the provider priority when the java perspective is active in the + * IDE + * + * @return the expression which can be empty or null. + */ + public String getExpression() { + return fExpression; + } + + /** + * Sets the expression to determine the priority of the provider. + * + * @param expression + * the expression, may be null. + * + * @see #getExpression() + */ + public void setExpression(String expression) { + fExpression = expression; + } +}
\ No newline at end of file |