/*******************************************************************************
* Copyright (c) 2006 IBM Corporation.
* 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 - Jeff Briggs, Henry Hughes, Ryan Morse
*******************************************************************************/
package org.eclipse.linuxtools.internal.systemtap.ui.ide.structures;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.IDEPlugin;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.Localization;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.preferences.IDEPreferenceConstants;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.preferences.PreferenceConstants;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.tparsers.FunctionParser;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.tparsers.ProbeParser;
import org.eclipse.linuxtools.internal.systemtap.ui.ide.structures.tparsers.SharedParser;
import org.eclipse.linuxtools.systemtap.structures.TreeNode;
import org.eclipse.linuxtools.systemtap.ui.consolelog.internal.ConsoleLogPlugin;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
/**
* This class is used for obtaining all probes and functions from the tapsets.
* If stored tapsets are in use, it will try to obtain the list from the TreeSettings memento.
* Otherwise, or if there is a problem with the memento, it will instead run the TapsetParsers
* in order to obtain tapset information.
* @author Ryan Morse
*/
public final class TapsetLibrary {
private static FunctionParser functionParser = FunctionParser.getInstance();
private static ProbeParser probeParser = ProbeParser.getInstance();
private static boolean initialized = false;
public static TreeNode getProbes() {
return probeParser.getTree();
}
public static TreeNode getStaticProbes() {
return getProbes().getChildByName(Messages.ProbeParser_staticProbes);
}
public static TreeNode getProbeAliases() {
return getProbes().getChildByName(Messages.ProbeParser_aliasProbes);
}
public static TreeNode[] getProbeCategoryNodes() {
return new TreeNode[] {getStaticProbes(), getProbeAliases()};
}
public static TreeNode getFunctions() {
return functionParser.getTree();
}
/**
* Initialize all listeners associated with loading tapset contents, and perform
* the first tapset load operation. Note that subsequent calls to this method will have no effect.
*/
public synchronized static void init() {
if (!initialized) {
initialized = true;
IPreferenceStore preferenceStore = IDEPlugin.getDefault().getPreferenceStore();
preferenceStore.addPropertyChangeListener(propertyChangeListener);
ConsoleLogPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(credentialChangeListener);
functionParser.addJobChangeListener(parseCompletionListener);
probeParser.addJobChangeListener(parseCompletionListener);
if (preferenceStore.getBoolean(IDEPreferenceConstants.P_STORED_TREE)
&& isTreeFileCurrent()) {
readTreeFile();
} else {
runStapParser();
}
}
}
private static final IPropertyChangeListener propertyChangeListener = event -> {
String property = event.getProperty();
if (property.equals(IDEPreferenceConstants.P_TAPSETS)) {
applyTapsetChanges((String) event.getOldValue(), (String) event.getNewValue());
} else if (property.equals(PreferenceConstants.P_ENV.SYSTEMTAP_TAPSET.toPrefKey())
|| property.equals(IDEPreferenceConstants.P_REMOTE_PROBES)) {
runStapParser();
} else if (property.equals(IDEPreferenceConstants.P_STORED_TREE)) {
if (event.getNewValue().equals(false)) {
// When turning off stored trees, reload the tapset contents directly.
TreeSettings.deleteTrees();
runStapParser();
} else if (isReady()) {
// When turning on stored trees, store the current trees immediately.
TreeSettings.setTrees(getFunctions(), getProbes());
}
}
};
private static final IPropertyChangeListener credentialChangeListener = event -> runStapParser();
private static JobChangeAdapter parseCompletionListener = new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
if (event.getResult().isOK()) {
if (isReady() && IDEPlugin.getDefault().getPreferenceStore().
getBoolean(IDEPreferenceConstants.P_STORED_TREE)) {
TreeSettings.setTrees(getFunctions(), getProbes());
}
if (event.getJob() instanceof ProbeParser) {
ManpageCacher.clear(TapsetItemType.PROBE, TapsetItemType.PROBEVAR);
} else {
ManpageCacher.clear(TapsetItemType.FUNCTION);
}
}
}
};
private static boolean isReady() {
IStatus probeResult = probeParser.getLatestResult();
IStatus funcResult = functionParser.getLatestResult();
return probeResult != null && funcResult != null
&& probeResult.isOK() && funcResult.isOK();
}
/**
* This method will trigger the appropriate parsing jobs
* to get the information directly from the files.
* If the jobs are already in progess, they will be restarted.
*/
public static void runStapParser() {
stop();
SharedParser.getInstance().clearTapsetContents();
functionParser.schedule();
probeParser.schedule();
}
private static void applyTapsetChanges(String oldTapsets, String newTapsets) {
List<String> oldList = Arrays.asList(oldTapsets.split(File.pathSeparator));
List<String> newList = Arrays.asList(newTapsets.split(File.pathSeparator));
List<String> additions = new ArrayList<>(newList);
additions.removeAll(oldList);
additions.remove(""); //$NON-NLS-1$
List<String> deletions = new ArrayList<>(oldList);
deletions.removeAll(newList);
deletions.remove(""); //$NON-NLS-1$
String[] additionArray = additions.toArray(new String[additions.size()]);
String[] deletionArray = deletions.toArray(new String[deletions.size()]);
SharedParser.getInstance().clearTapsetContents();
probeParser.runUpdate(additionArray, deletionArray);
functionParser.runUpdate(additionArray, deletionArray);
}
/**
* This method will get all of the tree information from the TreeSettings xml file.
*/
public static void readTreeFile() {
functionParser.setTree(TreeSettings.getFunctionTree());
probeParser.setTree(TreeSettings.getProbeTree());
}
/**
* This method checks to see if the tapsets have changed
* at all since the TreeSettings.xml file was created.
* @return boolean indicating whether or not the TreeSettings.xml file has the most up-to-date version
*/
private static boolean isTreeFileCurrent() {
long treesDate = TreeSettings.getTreeFileDate();
File f = getTapsetLocation();
if (f == null || !checkIsCurrentFolder(treesDate, f)) {
return false;
}
IPreferenceStore p = IDEPlugin.getDefault().getPreferenceStore();
String[] tapsets = p.getString(IDEPreferenceConstants.P_TAPSETS).split(File.pathSeparator);
if (!tapsets[0].trim().isEmpty()) {
for (int i = 0; i < tapsets.length; i++) {
f = new File(tapsets[i]);
if (!f.exists() || f.lastModified() > treesDate
|| f.canRead() && !checkIsCurrentFolder(treesDate, f)) {
return false;
}
}
}
return true;
}
/**
* This method attempts to locate the default tapset directory.
* @return File representing the default tapset location, or
* <code>null</code> if it cannot be found.
*/
public static File getTapsetLocation() {
final IPreferenceStore p = IDEPlugin.getDefault().getPreferenceStore();
File f = attemptToGetFileFrom(p.getString(PreferenceConstants.P_ENV.SYSTEMTAP_TAPSET.toPrefKey()));
if (f != null) {
return f;
}
f = attemptToGetFileFrom(System.getenv(PreferenceConstants.P_ENV.SYSTEMTAP_TAPSET.toEnvKey()));
if (f != null) {
return f;
}
f = attemptToGetFileFrom("/usr/share/systemtap/tapset"); //$NON-NLS-1$
if (f != null) {
return f;
}
f = attemptToGetFileFrom("/usr/local/share/systemtap/tapset"); //$NON-NLS-1$
if (f != null) {
return f;
}
Display.getDefault().asyncExec(() -> {
InputDialog i = new InputDialog(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
Localization.getString("TapsetBrowserView.TapsetLocation"), //$NON-NLS-1$
Localization.getString("TapsetBrowserView.WhereDefaultTapset"), null, null); //$NON-NLS-1$
i.open();
String path = i.getValue();
if (path != null) {
// This preference update should trigger a property listener
// that will update the tapset trees.
p.setValue(PreferenceConstants.P_ENV.SYSTEMTAP_TAPSET.toPrefKey(), i.getValue());
}
});
return null;
}
private static File attemptToGetFileFrom(String path) {
if (path == null) {
return null;
}
String trimmed = path.trim();
if (trimmed.isEmpty()) {
return null;
}
File f = new File(trimmed);
return f.exists() ? f : null;
}
/**
* This method checks the provided time stap against the folders
* time stamp. This is to see if the folder may have new data in it
* @param time The current time stamp
* @param folder The folder to check if it is newer the then time stamp
* @return boolean indicating whether the time stamp is newer then the folder
*/
private static boolean checkIsCurrentFolder(long time, File folder) {
File[] fs = folder.listFiles();
for (int i = 0; i < fs.length; i++) {
if (fs[i].lastModified() > time) {
return false;
}
if (fs[i].isDirectory() && fs[i].canRead()
&& !checkIsCurrentFolder(time, fs[i])) {
return false;
}
}
return true;
}
/**
* This method will stop all running tapset parsers, and will block
* the calling thread until they have terminated.
*/
public static void stop() {
functionParser.cancel();
try {
functionParser.join();
} catch (InterruptedException e) {
// The current thread was interrupted while waiting
// for the parser thread to exit. Nothing to do
// continue stopping.
}
probeParser.cancel();
try {
probeParser.join();
} catch (InterruptedException e) {}
}
}