1/*******************************************************************************
2 * Copyright (c) 2000, 2009 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *     IBM Corporation - initial API and implementation
10 *******************************************************************************/
11package org.eclipse.test.internal.performance.results.ui;
12
13
14import java.io.File;
15import java.io.PrintWriter;
16import java.io.StringWriter;
17import java.lang.reflect.InvocationTargetException;
18import java.util.Comparator;
19
20import org.eclipse.core.runtime.IProgressMonitor;
21import org.eclipse.core.runtime.IStatus;
22import org.eclipse.core.runtime.preferences.InstanceScope;
23import org.eclipse.jface.action.Action;
24import org.eclipse.jface.action.IMenuManager;
25import org.eclipse.jface.action.Separator;
26import org.eclipse.jface.dialogs.MessageDialog;
27import org.eclipse.jface.dialogs.ProgressMonitorDialog;
28import org.eclipse.jface.operation.IRunnableWithProgress;
29import org.eclipse.jface.resource.JFaceResources;
30import org.eclipse.jface.viewers.IStructuredSelection;
31import org.eclipse.jface.viewers.LabelProvider;
32import org.eclipse.jface.viewers.SelectionChangedEvent;
33import org.eclipse.jface.viewers.TreeViewer;
34import org.eclipse.jface.viewers.Viewer;
35import org.eclipse.jface.viewers.ViewerSorter;
36import org.eclipse.swt.SWT;
37import org.eclipse.swt.graphics.Color;
38import org.eclipse.swt.graphics.Font;
39import org.eclipse.swt.graphics.FontData;
40import org.eclipse.swt.widgets.Composite;
41import org.eclipse.test.internal.performance.results.db.DB_Results;
42import org.eclipse.test.internal.performance.results.model.BuildResultsElement;
43import org.eclipse.test.internal.performance.results.model.ResultsElement;
44import org.eclipse.test.internal.performance.results.utils.IPerformancesConstants;
45import org.eclipse.test.internal.performance.results.utils.Util;
46import org.eclipse.test.performance.ui.GenerateResults;
47import org.eclipse.ui.PlatformUI;
48import org.eclipse.ui.dialogs.ElementListSelectionDialog;
49import org.eclipse.ui.model.WorkbenchContentProvider;
50import org.eclipse.ui.model.WorkbenchLabelProvider;
51
52
53/**
54 * View to see all the builds which have performance results stored in the database.
55 * <p>
56 * Typical actions from this view are update local data files with builds results
57 * and generated the HTML pages.
58 * </p>
59 */
60public class BuildsView extends PerformancesView {
61
62	/**
63	 * Action to generate results.
64	 */
65	final class GenerateAction extends Action {
66		IStatus status;
67
68		public void run() {
69
70			// Ask for output directory
71			String resultGenerationDir = BuildsView.this.preferences.get(IPerformancesConstants.PRE_RESULTS_GENERATION_DIR, "");
72			String pathFilter = (BuildsView.this.outputDir == null) ? resultGenerationDir : BuildsView.this.outputDir.getPath();
73			File dir = changeDir(pathFilter, "Select directory to write comparison files");
74			if (dir == null) {
75				return;
76			}
77			BuildsView.this.outputDir = dir;
78			BuildsView.this.preferences.put(IPerformancesConstants.PRE_RESULTS_GENERATION_DIR, dir.getAbsolutePath());
79
80			// Select the reference
81			String[] baselines = BuildsView.this.results.getBaselines();
82			int bLength = baselines.length;
83			String selectedBaseline;
84			switch (bLength) {
85				case 0:
86					// no baseline, nothing to do...
87					selectedBaseline = BuildsView.this.results.getPerformanceResults().getBaselineName();
88					break;
89				case 1:
90					// only one baseline, no selection to do
91					selectedBaseline = baselines[0];
92					break;
93				default:
94					// select the baseline from list
95					ElementListSelectionDialog dialog = new ElementListSelectionDialog(getSite().getShell(), new LabelProvider());
96					dialog.setTitle(getTitleToolTip());
97					dialog.setMessage("Select the baseline to use while generating results:");
98					String[] defaultBaseline = new String[] { baselines[baselines.length - 1] };
99					dialog.setInitialSelections(defaultBaseline);
100					dialog.setElements(baselines);
101					dialog.open();
102					Object[] selected = dialog.getResult();
103					if (selected == null)
104						return;
105					selectedBaseline = (String) selected[0];
106					break;
107			}
108			final String baselineName = selectedBaseline;
109			BuildsView.this.results.getPerformanceResults().setBaselineName(baselineName);
110
111			// Ask for fingerprints
112			final boolean fingerprints = MessageDialog.openQuestion(BuildsView.this.shell, getTitleToolTip(), "Generate only fingerprints?");
113
114			// Generate all selected builds
115			int length = BuildsView.this.buildsResults.length;
116			for (int i = 0; i < length; i++) {
117				generate(i, baselineName, fingerprints);
118			}
119		}
120
121		/*
122		 * Generate the HTML pages.
123		 */
124		private void generate(int i, final String baselineName, final boolean fingerprints) {
125			// Create output directory
126			final String buildName = BuildsView.this.buildsResults[i].getName();
127			final File genDir = new File(BuildsView.this.outputDir, buildName);
128			if (!genDir.exists() && !genDir.mkdir()) {
129				MessageDialog.openError(BuildsView.this.shell, getTitleToolTip(), "Cannot create " + genDir.getPath() + " to generate results!");
130				return;
131			}
132
133			// Create runnable
134			IRunnableWithProgress runnable = new IRunnableWithProgress() {
135
136				public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
137					try {
138						monitor.beginTask("Generate performance results", 10000);
139						GenerateResults generation = new GenerateResults(BuildsView.this.results.getPerformanceResults(),
140						    buildName,
141						    baselineName,
142						    fingerprints,
143						    BuildsView.this.dataDir,
144						    genDir);
145						GenerateAction.this.status = generation.run(monitor);
146						monitor.done();
147					} catch (Exception e) {
148						e.printStackTrace();
149					}
150				}
151			};
152
153			// Run with progress monitor
154			ProgressMonitorDialog readProgress = new ProgressMonitorDialog(getSite().getShell());
155			try {
156				readProgress.run(true, true, runnable);
157			} catch (InvocationTargetException e) {
158				// skip
159			} catch (InterruptedException e) {
160				// skip
161			}
162
163			// Results
164			if (!this.status.isOK()) {
165				StringWriter swriter = new StringWriter();
166				PrintWriter pwriter = new PrintWriter(swriter);
167				swriter.write(this.status.getMessage());
168				Throwable ex = this.status.getException();
169				if (ex != null) {
170					swriter.write(": ");
171					swriter.write(ex.getMessage());
172					swriter.write('\n');
173					ex.printStackTrace(pwriter);
174				}
175				MessageDialog.open(this.status.getSeverity(),
176				    BuildsView.this.shell,
177				    getTitleToolTip(),
178				    swriter.toString(),
179				    SWT.NONE);
180			}
181		}
182	}
183
184	/**
185	 * Action to update local data files with the performance results of a build.
186	 *
187	 * This may be done lazily (i.e. not done if the local data already knows
188	 * the build) or forced (i.e. done whatever the local data files contain).
189	 */
190	class UpdateBuildAction extends Action {
191
192		boolean force;
193
194		UpdateBuildAction(boolean force) {
195			super();
196			this.force = force;
197		}
198
199		public void run() {
200
201			// Verify that directories are set
202			if (BuildsView.this.dataDir == null) {
203				if (changeDataDir() == null) {
204					if (!MessageDialog.openConfirm(BuildsView.this.shell, getTitleToolTip(), "No local files directory is set, hence the update could not be written! OK to continue?")) {
205						return;
206					}
207				}
208			}
209
210			// Progress dialog
211			IRunnableWithProgress runnable = new IRunnableWithProgress() {
212
213				public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
214					try {
215						updateBuilds(monitor);
216					} catch (Exception e) {
217						e.printStackTrace();
218					}
219				}
220			};
221			ProgressMonitorDialog readProgress = new ProgressMonitorDialog(getSite().getShell());
222			try {
223				readProgress.run(true, true, runnable);
224			} catch (InvocationTargetException e) {
225				return;
226			} catch (InterruptedException e) {
227				return;
228			}
229
230			// Reset Components and Builds views input
231			refreshInput();
232			getSiblingView().refreshInput();
233		}
234
235		void updateBuilds(IProgressMonitor monitor) {
236			BuildsView.this.updateBuilds(monitor, this.force);
237		}
238	}
239
240	/**
241	 * Action to update local data files with the performance results of all builds.
242	 *
243	 * This may be done lazily (i.e. not done if the local data already knows
244	 * the build) or forced (i.e. done whatever the local data files contain).
245	 */
246	class UpdateAllBuildsAction extends UpdateBuildAction {
247
248		UpdateAllBuildsAction(boolean force) {
249			super(force);
250		}
251//
252//		public boolean isEnabled() {
253//			String[] elements = buildsToUpdate();
254//			return elements != null;
255//		}
256
257		void updateBuilds(IProgressMonitor monitor) {
258			BuildsView.this.updateAllBuilds(monitor, this.force);
259		}
260	}
261
262	/**
263	 * Class to compare builds regarding their date instead of their name.
264	 *
265	 * @see Util#getBuildDate(String)
266	 */
267	class BuildDateComparator implements Comparator {
268		public int compare(Object o1, Object o2) {
269	        String s1 = (String) o1;
270	        String s2 = (String) o2;
271	        return Util.getBuildDate(s1).compareTo(Util.getBuildDate(s2));
272	    }
273	}
274
275	// Views
276	PerformancesView componentsView;
277
278	// Results model
279	BuildResultsElement[] buildsResults;
280
281	// Generation info
282	File outputDir;
283
284	// Actions
285	Action generate;
286	UpdateBuildAction updateBuild, updateAllBuilds;
287//	UpdateBuildAction forceUpdateBuild, forceUpdateAllBuilds;
288
289	// SWT resources
290	Font italicFont;
291
292/*
293 * Default constructor.
294 */
295public BuildsView() {
296	this.preferences = new InstanceScope().getNode(IPerformancesConstants.PLUGIN_ID);
297	this.preferences.addPreferenceChangeListener(this);
298}
299
300/*
301 * Compute the list of builds to update based on their status.
302 */
303String[] buildsToUpdate() {
304	Object[] elements = this.results.getBuilds();
305	int length = elements.length;
306	String[] buildsToUpdate = new String[length];
307	int count = 0;
308	for (int i=0; i<length; i++) {
309		BuildResultsElement element = (BuildResultsElement) elements[i];
310		if (element.getStatus() == 0) {
311	        buildsToUpdate[count++] = element.getName();
312		}
313	}
314	if (count == 0) return null;
315	if (count < length) {
316		System.arraycopy(buildsToUpdate, 0, buildsToUpdate = new String[count], 0, count);
317	}
318	return buildsToUpdate;
319}
320
321/* (non-Javadoc)
322 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#createPartControl(org.eclipse.swt.widgets.Composite)
323 */
324public void createPartControl(Composite parent) {
325	super.createPartControl(parent);
326
327	// Create the viewer
328	this.viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
329
330	// Set the content provider: first level is builds list
331	WorkbenchContentProvider contentProvider = new WorkbenchContentProvider() {
332		public Object[] getElements(Object o) {
333			return getBuilds();
334		}
335	};
336	this.viewer.setContentProvider(contentProvider);
337
338	// Set the label provider
339	WorkbenchLabelProvider labelProvider = new WorkbenchLabelProvider() {
340
341		// Set an italic font when no local data have been read
342		public Font getFont(Object element) {
343			Font font = super.getFont(element);
344			if (element instanceof BuildResultsElement) {
345				if (((BuildResultsElement) element).isUnknown()) {
346					if (BuildsView.this.italicFont == null) {
347						FontData[] defaultFont = JFaceResources.getDefaultFont().getFontData();
348						FontData italicFontData = new FontData(defaultFont[0].getName(), defaultFont[0].getHeight(), SWT.ITALIC);
349						BuildsView.this.italicFont = new Font(DEFAULT_DISPLAY, italicFontData);
350					}
351					return BuildsView.this.italicFont;
352				}
353			}
354			return font;
355		}
356
357		// Set font in gray when no local data is available (i.e. local data needs to be updated)
358		public Color getForeground(Object element) {
359			Color color = super.getForeground(element);
360			if (element instanceof BuildResultsElement) {
361				if (!((BuildResultsElement) element).isRead()) {
362					color = DARK_GRAY;
363				}
364			}
365			return color;
366		}
367	};
368	this.viewer.setLabelProvider(labelProvider);
369
370	// Set the children sorter
371	ViewerSorter nameSorter = new ViewerSorter() {
372
373		// Sort children using specific comparison (see the implementation
374		// of the #compareTo(Object) in the ResultsElement hierarchy
375		public int compare(Viewer view, Object e1, Object e2) {
376			if (e2 instanceof ResultsElement) {
377				return ((ResultsElement) e2).compareTo(e1);
378			}
379			return super.compare(view, e1, e2);
380		}
381	};
382	this.viewer.setSorter(nameSorter);
383
384	// Finalize viewer initialization
385	PlatformUI.getWorkbench().getHelpSystem().setHelp(this.viewer.getControl(), "org.eclipse.test.performance.ui.builds");
386	finalizeViewerCreation();
387}
388
389/* (non-Javadoc)
390 * @see org.eclipse.ui.part.WorkbenchPart#dispose()
391 */
392public void dispose() {
393	if (this.italicFont != null) {
394		this.italicFont.dispose();
395	}
396	super.dispose();
397}
398
399/*
400 * (non-Javadoc)
401 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#fillContextMenu(org.eclipse.jface.action.IMenuManager)
402 */
403void fillContextMenu(IMenuManager manager) {
404	super.fillContextMenu(manager);
405	manager.add(this.generate);
406	manager.add(this.updateBuild);
407//	manager.add(this.forceUpdateBuild);
408}
409
410/*
411 * (non-Javadoc)
412 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#fillLocalPullDown(org.eclipse.jface.action.IMenuManager)
413 */
414void fillFiltersDropDown(IMenuManager manager) {
415	super.fillFiltersDropDown(manager);
416	manager.add(this.filterLastBuilds);
417}
418
419/*
420 * Fill the local data drop-down menu
421 */
422void fillLocalDataDropDown(IMenuManager manager) {
423	super.fillLocalDataDropDown(manager);
424	manager.add(new Separator());
425	manager.add(this.updateAllBuilds);
426//	manager.add(this.forceUpdateAllBuilds);
427}
428
429/*
430 * Get all builds from the model.
431 */
432Object[] getBuilds() {
433	if (this.results == null) {
434		initResults();
435	}
436	return this.results.getBuilds();
437}
438
439/*
440 * Return the components view.
441 */
442PerformancesView getSiblingView() {
443	if (this.componentsView == null) {
444		this.componentsView = (PerformancesView) getWorkbenchView("org.eclipse.test.internal.performance.results.ui.ComponentsView");
445	}
446	return this.componentsView;
447}
448
449/*
450 * (non-Javadoc)
451 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#makeActions()
452 */
453void makeActions() {
454
455	super.makeActions();
456
457	// Generate action
458	this.generate = new GenerateAction();
459	this.generate.setText("&Generate");
460
461	// Update build actions
462	boolean connected = this.preferences.getBoolean(IPerformancesConstants.PRE_DATABASE_CONNECTION, IPerformancesConstants.DEFAULT_DATABASE_CONNECTION);
463	this.updateBuild = new UpdateBuildAction(false);
464	this.updateBuild.setText("&Update from DB");
465	this.updateBuild.setEnabled(connected);
466//	this.forceUpdateBuild = new UpdateBuildAction(true);
467//	this.forceUpdateBuild.setText("Force Update");
468
469	// Update build action
470	this.updateAllBuilds = new UpdateAllBuildsAction(false);
471	this.updateAllBuilds.setText("&Update from DB (all)");
472	this.updateAllBuilds.setEnabled(connected);
473//	this.forceUpdateAllBuilds = new UpdateAllBuildsAction(true);
474//	this.forceUpdateAllBuilds.setText("Force Update all");
475
476	// Set filters default
477	this.filterBaselineBuilds.setChecked(false);
478	this.filterNightlyBuilds.setChecked(false);
479}
480
481/**
482 * Reset the views.
483 */
484public void resetView() {
485
486	boolean debug = true;
487
488	// Look whether database constants has changed or not
489	int eclipseVersion = this.preferences.getInt(IPerformancesConstants.PRE_ECLIPSE_VERSION, IPerformancesConstants.DEFAULT_ECLIPSE_VERSION);
490	boolean connected = this.preferences.getBoolean(IPerformancesConstants.PRE_DATABASE_CONNECTION, IPerformancesConstants.DEFAULT_DATABASE_CONNECTION);
491	String databaseLocation = this.preferences.get(IPerformancesConstants.PRE_DATABASE_LOCATION, IPerformancesConstants.NETWORK_DATABASE_LOCATION);
492	String lastBuild = this.preferences.get(IPerformancesConstants.PRE_LAST_BUILD, null);
493	boolean noLastBuild = lastBuild.length() == 0;
494	if (debug) {
495		System.out.println("Reset View:");
496		System.out.println("	- eclispe version = "+eclipseVersion);
497		System.out.println("	- connected       = "+connected);
498		System.out.println("	- db location     = "+databaseLocation);
499		System.out.println("	- last build      = "+(noLastBuild?"<none>":lastBuild));
500	}
501	final boolean sameVersion = DB_Results.getDbVersion().endsWith(Integer.toString(eclipseVersion));
502	final boolean sameConnection = connected == DB_Results.DB_CONNECTION;
503	final boolean sameDB = sameVersion && databaseLocation.equals(DB_Results.getDbLocation());
504	boolean sameLastBuild = (noLastBuild && LAST_BUILD == null) || lastBuild.equals(LAST_BUILD);
505	if (debug) {
506		System.out.println("	- same version:    "+sameVersion);
507		System.out.println("	- same connection: "+sameConnection);
508		System.out.println("	- same DB:         "+sameDB);
509		System.out.println("	- same last build: "+sameLastBuild);
510	}
511	final PerformancesView siblingView = getSiblingView();
512	if (sameConnection && sameDB) {
513		if (!sameLastBuild) {
514			// Set last build
515			LAST_BUILD = noLastBuild ? null : lastBuild;
516			this.results.setLastBuildName(LAST_BUILD);
517			siblingView.results.setLastBuildName(LAST_BUILD);
518
519			// Reset views content
520			resetInput();
521			siblingView.resetInput();
522
523			// May be read local data now
524			File newDataDir = changeDataDir();
525			if (newDataDir == null) {
526				this.dataDir = null;
527				siblingView.dataDir = null;
528			}
529		}
530		// No database preferences has changed do nothing
531		return;
532	}
533
534	// Update database constants
535	boolean updated = DB_Results.updateDbConstants(connected, eclipseVersion, databaseLocation);
536	if (debug) {
537		System.out.println("	- updated:         "+updated);
538	}
539	if (!connected) {
540		if (!updated) {
541			MessageDialog.openError(this.shell, getTitleToolTip(), "Error while updating database results constants!\nOpen error log to see more details on this error");
542		}
543	} else if (updated) {
544		StringBuffer message = new StringBuffer("Database connection has been correctly ");
545		message.append( connected ? "opened." : "closed.");
546		MessageDialog.openInformation(this.shell, getTitleToolTip(), message.toString());
547	} else {
548		MessageDialog.openError(this.shell, getTitleToolTip(), "The database connection cannot be established!\nOpen error log to see more details on this error");
549		DB_Results.updateDbConstants(false, eclipseVersion, databaseLocation);
550	}
551	setTitleToolTip();
552	siblingView.setTitleToolTip();
553
554	// Refresh view
555	if (sameVersion && sameLastBuild) {
556		// Refresh only builds view as the sibling view (Components) contents is based on local data files contents
557		this.results.resetBuildNames();
558		refreshInput();
559	} else {
560		// Reset views content
561		resetInput();
562		siblingView.resetInput();
563
564		// May be read local data now
565		if (MessageDialog.openQuestion(this.shell, getTitleToolTip(), "Do you want to read local data right now?")) {
566			changeDataDir();
567		} else {
568			this.dataDir = null;
569			siblingView.dataDir = null;
570		}
571	}
572
573	// Update actions
574	this.updateBuild.setEnabled(connected);
575	this.updateAllBuilds.setEnabled(connected);
576}
577
578/*
579 * (non-Javadoc)
580 * @see org.eclipse.test.internal.performance.results.ui.PerformancesView#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
581 */
582public void selectionChanged(SelectionChangedEvent event) {
583	super.selectionChanged(event);
584
585	// Update selected element
586	Object selection = this.viewer.getSelection();
587	int length = 0;
588	if (selection instanceof IStructuredSelection) {
589		Object[] elements = ((IStructuredSelection)selection).toArray();
590		length = elements == null ? 0 : elements.length;
591		this.buildsResults = new BuildResultsElement[length];
592		if (length == 0) {
593			this.updateAllBuilds.setText("&Update from DB (all)");
594			return;
595		}
596		for (int i=0; i<length; i++) {
597			this.buildsResults[i] = (BuildResultsElement) elements[i];
598		}
599	} else {
600		return;
601	}
602
603	// Update update build action
604//	boolean enableUpdateBuild = true;
605//	boolean enableGenerate = true;
606	int readBuilds = 0;
607	for (int i=0; i<length; i++) {
608		if (this.buildsResults[i].isRead()) {
609//			enableUpdateBuild = false;
610			readBuilds++;
611		} else {
612//			enableGenerate = false;
613		}
614	}
615//	this.updateBuild.setEnabled(enableUpdateBuild);
616//	this.forceUpdateBuild.setEnabled(!enableUpdateBuild);
617	final boolean force = readBuilds < length;
618	this.updateBuild.force = force;
619	this.updateAllBuilds.force = force;
620	this.updateAllBuilds.setText("&Update from DB");
621
622	// Update generate action
623	boolean enableGenerate = true;
624	if (enableGenerate) {
625		for (int i=0; i<length; i++) {
626			if (this.buildsResults[i].getName().startsWith(DB_Results.getDbBaselinePrefix())) {
627				enableGenerate = false;
628				break;
629			}
630		}
631	}
632	this.generate.setEnabled(enableGenerate);
633}
634
635void updateAllBuilds(IProgressMonitor monitor, boolean force) {
636	if (this.dataDir == null) {
637		changeDataDir();
638	}
639	String[] builds = buildsToUpdate();
640	if (builds == null) {
641		this.results.updateBuild(null, true, this.dataDir, monitor);
642	} else {
643		this.results.updateBuilds(builds, force, this.dataDir, monitor);
644	}
645}
646
647void updateBuilds(IProgressMonitor monitor, boolean force) {
648	if (this.dataDir == null) {
649		changeDataDir();
650	}
651	int length = this.buildsResults.length;
652	String[] builds = new String[length];
653	for (int i = 0; i < length; i++) {
654		builds[i] = this.buildsResults[i].getName();
655	}
656	this.results.updateBuilds(builds, force, this.dataDir, monitor);
657}
658
659}