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.utils;
12
13import java.io.File;
14import java.io.FileInputStream;
15import java.io.FileNotFoundException;
16import java.io.FileOutputStream;
17import java.io.IOException;
18import java.io.InputStream;
19import java.io.OutputStream;
20import java.text.NumberFormat;
21import java.text.ParseException;
22import java.text.SimpleDateFormat;
23import java.util.ArrayList;
24import java.util.Calendar;
25import java.util.Comparator;
26import java.util.Date;
27import java.util.GregorianCalendar;
28import java.util.List;
29import java.util.Locale;
30import java.util.StringTokenizer;
31
32import org.eclipse.core.runtime.preferences.IEclipsePreferences;
33import org.eclipse.test.internal.performance.results.db.BuildResults;
34import org.eclipse.test.internal.performance.results.db.DB_Results;
35
36/**
37 * Utility methods for statistics. Got from org.eclipse.test.performance
38 * framework
39 */
40public final class Util implements IPerformancesConstants {
41
42	// Percentages
43	public static final NumberFormat PERCENTAGE_FORMAT = NumberFormat.getPercentInstance(Locale.US);
44	static {
45		PERCENTAGE_FORMAT.setMaximumFractionDigits(2);
46	}
47	public static final NumberFormat DOUBLE_FORMAT = NumberFormat.getNumberInstance(Locale.US);
48	static {
49		DOUBLE_FORMAT.setMaximumFractionDigits(2);
50	}
51
52	// Strings
53	public static final String LINE_SEPARATOR = System.getProperty("line.separator");
54
55	// Build prefixes
56	public static final List ALL_BUILD_PREFIXES = new ArrayList(3);
57	static {
58		ALL_BUILD_PREFIXES.add("I");
59		ALL_BUILD_PREFIXES.add("N");
60		ALL_BUILD_PREFIXES.add("M");
61	}
62	public static final List BUILD_PREFIXES = new ArrayList(2);
63	static {
64		BUILD_PREFIXES.add("I");
65		BUILD_PREFIXES.add("N");
66	}
67	public static final List MAINTENANCE_BUILD_PREFIXES = new ArrayList(2);
68	static {
69		MAINTENANCE_BUILD_PREFIXES.add("I");
70		MAINTENANCE_BUILD_PREFIXES.add("M");
71	}
72	public static final List BASELINE_BUILD_PREFIXES = new ArrayList(1);
73	static {
74		BASELINE_BUILD_PREFIXES.add(DB_Results.getDbBaselinePrefix());
75	}
76
77	// Milestones constants
78	private static String[] MILESTONES;
79	public static final BuildDateComparator BUILD_DATE_COMPARATOR = new BuildDateComparator();
80
81static class BuildDateComparator implements Comparator {
82	public int compare(Object o1, Object o2) {
83		String s1 = (String) o1;
84		String s2 = (String) o2;
85		return getBuildDate(s1).compareTo(getBuildDate(s2));
86	}
87}
88
89private static void initMilestones() {
90	String version = DB_Results.getDbVersion();
91
92	// Initialize reference version and database directory
93	char mainVersion = version.charAt(1);
94	char minorVersion = version.charAt(2);
95
96	// Initialize milestones
97	if (mainVersion == '3') {
98		switch (minorVersion) {
99			case '3':
100			case '4':
101				throw new RuntimeException("Version "+mainVersion+'.'+minorVersion+" is no longer supported!");
102			case '5':
103				MILESTONES = V35_MILESTONES;
104				break;
105			case '6':
106				MILESTONES = V36_MILESTONES;
107				break;
108			default:
109				throw new RuntimeException("Version "+mainVersion+'.'+minorVersion+" is not supported yet!");
110		}
111	} else {
112		throw new RuntimeException("Version "+mainVersion+'.'+minorVersion+" is not supported yet!");
113	}
114}
115
116// Static information for time and date
117public static final int ONE_MINUTE = 60000;
118public static final long ONE_HOUR = 3600000L;
119public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMddHHmm"); //$NON-NLS-1$
120
121/**
122 * Compute the student t-test values.
123 *
124 * @see "http://en.wikipedia.org/wiki/Student's_t-test"
125 *
126 * @param baselineResults The baseline build
127 * @param buildResults The current build
128 * @return The student t-test value as a double.
129 */
130public static double computeTTest(BuildResults baselineResults, BuildResults buildResults) {
131
132	double ref = baselineResults.getValue();
133	double val = buildResults.getValue();
134
135	double delta = ref - val;
136	long dfRef = baselineResults.getCount() - 1;
137	double sdRef = baselineResults.getDeviation();
138	long dfVal = buildResults.getCount() - 1;
139	double sdVal = buildResults.getDeviation();
140	// TODO if the stdev's are not sufficiently similar, we have to take a
141	// different approach
142
143	if (!Double.isNaN(sdRef) && !Double.isNaN(sdVal) && dfRef > 0 && dfVal > 0) {
144		long df = dfRef + dfVal;
145		double sp_square = (dfRef * sdRef * sdRef + dfVal * sdVal * sdVal) / df;
146
147		double se_diff = Math.sqrt(sp_square * (1.0 / (dfRef + 1) + 1.0 / (dfVal + 1)));
148		double t = Math.abs(delta / se_diff);
149		return t;
150	}
151
152	return -1;
153}
154
155/**
156 * Copy a file to another location.
157 *
158 * @param src the source file.
159 * @param dest the destination.
160 * @return <code>true</code> if the file was successfully copied,
161 * 	<code>false</code> otherwise.
162 */
163public static boolean copyFile(File src, File dest) {
164
165	try {
166		InputStream in = new FileInputStream(src);
167		OutputStream out = new FileOutputStream(dest);
168		byte[] buf = new byte[1024];
169		int len;
170		while ((len = in.read(buf)) > 0) {
171			out.write(buf, 0, len);
172		}
173		in.close();
174		out.close();
175	} catch (FileNotFoundException e) {
176		e.printStackTrace();
177		return false;
178	} catch (IOException e) {
179		e.printStackTrace();
180		return false;
181	}
182	return true;
183}
184
185/**
186 * Copy a file content to another location.
187 *
188 * @param in the input stream.
189 * @param dest the destination.
190 * @return <code>true</code> if the file was successfully copied,
191 * 	<code>false</code> otherwise.
192 */
193public static boolean copyStream(InputStream in, File dest) {
194
195	try {
196		OutputStream out = new FileOutputStream(dest);
197		byte[] buf = new byte[1024];
198		int len;
199		while ((len = in.read(buf)) > 0) {
200			out.write(buf, 0, len);
201		}
202		in.close();
203		out.close();
204	} catch (FileNotFoundException e) {
205		e.printStackTrace();
206		return false;
207	} catch (IOException e) {
208		e.printStackTrace();
209		return false;
210	}
211	return true;
212}
213
214/**
215 * Return the build date as yyyyMMddHHmm.
216 *
217 * @param buildName The build name (e.g. I20090806-0100)
218 * @return The date as a string.
219 */
220public static String getBuildDate(String buildName) {
221	return getBuildDate(buildName, DB_Results.getDbBaselinePrefix());
222}
223
224/**
225 * Return the build date as yyyyMMddHHmm.
226 *
227 * @param buildName The build name (e.g. I20090806-0100)
228 * @param baselinePrefix The baseline prefix (e.g. {@link DB_Results#getDbBaselinePrefix()})
229 * @return The date as a string.
230 */
231public static String getBuildDate(String buildName, String baselinePrefix) {
232
233	// Baseline name
234	if (baselinePrefix != null && buildName.startsWith(baselinePrefix)) {
235		int length = buildName.length();
236		return buildName.substring(length-12, length);
237	}
238
239	// Build name
240	char first = buildName.charAt(0);
241	if (first == 'N' || first == 'I' || first == 'M') { // TODO (frederic) should be buildIdPrefixes...
242		return buildName.substring(1, 9)+buildName.substring(10, 14);
243	}
244
245	// Try with date format
246	int length = buildName.length() - 12 /* length of date */;
247	for (int i=0; i<=length; i++) {
248		try {
249			String substring = i == 0 ? buildName : buildName.substring(i);
250			Util.DATE_FORMAT.parse(substring);
251			return substring; // if no exception is raised then the substring has a correct date format => return it
252		} catch(ParseException ex) {
253			// skip
254		}
255	}
256	return null;
257}
258
259/**
260 * Returns the date of the milestone corresponding at the given index.
261 *
262 * @param index The index of the milestone
263 * @return The date as a YYYYMMDD-hhmm string.
264 */
265public static String getMilestoneDate(int index) {
266	int length = getMilestonesLength();
267	if (index >= length) return null;
268	int dash = MILESTONES[index].indexOf('-');
269	return MILESTONES[index].substring(dash+1);
270}
271
272/**
273 * Returns the milestone matching the given build name.
274 *
275 * @param buildName The name of the build
276 * @return The milestone as a string (e.g. M1)
277 */
278public static String getMilestone(String buildName) {
279	if (buildName != null && buildName.length() >= 12) {
280		int length = getMilestonesLength();
281		String buildDate = getBuildDate(buildName, DB_Results.getDbBaselinePrefix());
282		for (int i=0; i<length; i++) {
283			int start = MILESTONES[i].indexOf(buildDate);
284			if (start > 0) {
285				return MILESTONES[i];
286			}
287		}
288	}
289	return null;
290}
291
292/**
293 * Returns the name the milestone matching the given build name.
294 *
295 * @param buildName The name of the build
296 * @return The milestone name as a string (e.g. M1)
297 */
298public static String getMilestoneName(String buildName) {
299	if (buildName != null && buildName.length() >= 12) {
300		int length = getMilestonesLength();
301		String buildDate = getBuildDate(buildName, DB_Results.getDbBaselinePrefix());
302		for (int i=0; i<length; i++) {
303			int start = MILESTONES[i].indexOf(buildDate);
304			if (start > 0) {
305				return MILESTONES[i].substring(0, start - 1);
306			}
307		}
308	}
309	return null;
310}
311
312/**
313 * Returns whether the given build name is a milestone or not.
314 *
315 * @param buildName The build name
316 * @return <code>true</code> if the build name matches a milestone one,
317 * 	<code>false</code> otherwise.
318 */
319public static boolean isMilestone(String buildName) {
320	return getMilestoneName(buildName) != null;
321}
322
323/**
324 * Returns the name of the milestone which run after the given build name
325 * or <code>null</code> if there's no milestone since the build has run.
326 *
327 * @param buildName The build name
328 * @return <code>true</code> if the build name matches a milestone one,
329 * 	<code>false</code> otherwise.
330 */
331public static String getNextMilestone(String buildName) {
332	int length = getMilestonesLength();
333	String buildDate = getBuildDate(buildName);
334	for (int i=0; i<length; i++) {
335		String milestoneDate = MILESTONES[i].substring(MILESTONES[i].indexOf('-')+1);
336		if (milestoneDate.compareTo(buildDate) > 0) {
337			return milestoneDate;
338		}
339	}
340	return null;
341}
342
343/**
344 * Return the number of milestones.
345 *
346 * @return The number as an int
347 */
348public static int getMilestonesLength() {
349	if (MILESTONES == null) initMilestones();
350	int length = MILESTONES.length;
351	return length;
352}
353
354/**
355 * @deprecated
356 */
357public static boolean matchPattern(String name, String pattern) {
358	if (pattern.equals("*")) return true; //$NON-NLS-1$
359	if (pattern.indexOf('*') < 0 && pattern.indexOf('?') < 0) {
360		pattern += "*"; //$NON-NLS-1$
361	}
362	StringTokenizer tokenizer = new StringTokenizer(pattern, "*?", true); //$NON-NLS-1$
363	int start = 0;
364	String previous = ""; //$NON-NLS-1$
365	while (tokenizer.hasMoreTokens()) {
366		String token = tokenizer.nextToken();
367		if (!token.equals("*") && !token.equals("?")) { //$NON-NLS-1$ //$NON-NLS-2$
368			if (previous.equals("*")) { //$NON-NLS-1$
369				int idx = name.substring(start).indexOf(token);
370				if (idx < 0) return false;
371				start += idx;
372			} else {
373				if (previous.equals("?")) start++; //$NON-NLS-1$
374				if (!name.substring(start).startsWith(token)) return false;
375			}
376			start += token.length();
377		}
378		previous = token;
379	}
380	if (previous.equals("*")) { //$NON-NLS-1$
381		return true;
382	} else if (previous.equals("?")) { //$NON-NLS-1$
383		return name.length() == start;
384	}
385	return name.endsWith(previous);
386}
387
388/**
389 * @deprecated
390 */
391public static double round(double value) {
392	return Math.round(value * 10000) / 10000.0;
393}
394
395/**
396 * @deprecated
397 */
398public static double round(double value, int precision) {
399	if (precision < 0) {
400		throw new IllegalArgumentException("Should have a precision at least greater than 0!");
401	}
402	if (precision == 0) return (long) Math.floor(value);
403	double factor = 10;
404	int n = 1;
405	while (n++ < precision)
406		factor *= 10;
407	return Math.round(value * factor) / factor;
408}
409
410/**
411 * Returns a string to display the given time as a duration
412 * formatted as "hh:mm:ss".
413 *
414 * @param time The time to format as a long.
415 * @return The formatted string.
416 */
417public static String timeChrono(long time) {
418	if (time < 1000) { // less than 1s
419		return "00:00:00"; //$NON-NLS-1$
420	}
421	StringBuffer buffer = new StringBuffer();
422	int seconds = (int) (time / 1000);
423	if (seconds < 60) {
424		buffer.append("00:00:"); //$NON-NLS-1$
425		if (seconds < 10) buffer.append('0');
426		buffer.append(seconds);
427	} else {
428		int minutes = seconds / 60;
429		if (minutes < 60) {
430			buffer.append("00:"); //$NON-NLS-1$
431			if (minutes < 10) buffer.append('0');
432			buffer.append(minutes);
433			buffer.append(':');
434			seconds = seconds % 60;
435			if (seconds < 10) buffer.append('0');
436			buffer.append(seconds);
437		} else {
438			int hours = minutes / 60;
439			if (hours < 10) buffer.append('0');
440			buffer.append(hours);
441			buffer.append(':');
442			minutes = minutes % 60;
443			if (minutes < 10) buffer.append('0');
444			buffer.append(minutes);
445			buffer.append(':');
446			seconds = seconds % 60;
447			if (seconds < 10) buffer.append('0');
448			buffer.append(seconds);
449		}
450	}
451	return buffer.toString();
452}
453
454/**
455 * Returns a string to display the given time as the hour of the day
456 * formatted as "hh:mm:ss".
457 *
458 * @param time The time to format as a long.
459 * @return The formatted string.
460 */
461public static String timeEnd(long time) {
462	GregorianCalendar calendar = new GregorianCalendar();
463	calendar.add(Calendar.SECOND, (int)(time/1000));
464	Date date = calendar.getTime();
465	SimpleDateFormat dateFormat = new SimpleDateFormat("KK:mm:ss"); //$NON-NLS-1$
466	return dateFormat.format(date);
467}
468
469/**
470 * Returns a string to display the given time as a duration
471 * formatted as:
472 *	<ul>
473 *	<li>"XXXms" if the duration is less than 0.1s (e.g. "543ms")</li>
474 *	<li>"X.YYs" if the duration is less than 1s (e.g. "5.43s")</li>
475 *	<li>"XX.Ys" if the duration is less than 1mn (e.g. "54.3s")</li>
476 *	<li>"XXmn XXs" if the duration is less than 1h (e.g. "54mn 3s")</li>
477 *	<li>"XXh XXmn XXs" if the duration is over than 1h (e.g. "5h 4mn 3s")</li>
478 *	</ul>
479 *
480 * @param time The time to format as a long.
481 * @return The formatted string.
482 */
483public static String timeString(long time) {
484	NumberFormat format = NumberFormat.getInstance();
485	format.setMaximumFractionDigits(1);
486	StringBuffer buffer = new StringBuffer();
487	if (time == 0) {
488		// print nothing
489	} if (time < 100) { // less than 0.1s
490		buffer.append(time);
491		buffer.append("ms"); //$NON-NLS-1$
492	} else if (time < 1000) { // less than 1s
493		if ((time%100) != 0) {
494			format.setMaximumFractionDigits(2);
495		}
496		buffer.append(format.format(time/1000.0));
497		buffer.append("s"); //$NON-NLS-1$
498	} else if (time < Util.ONE_MINUTE) {  // less than 1mn
499		if ((time%1000) == 0) {
500			buffer.append(time/1000);
501		} else {
502			buffer.append(format.format(time/1000.0));
503		}
504		buffer.append("s"); //$NON-NLS-1$
505	} else if (time < Util.ONE_HOUR) {  // less than 1h
506		buffer.append(time/Util.ONE_MINUTE).append("mn "); //$NON-NLS-1$
507		long seconds = time%Util.ONE_MINUTE;
508		buffer.append(seconds/1000);
509		buffer.append("s"); //$NON-NLS-1$
510	} else {  // more than 1h
511		long h = time / Util.ONE_HOUR;
512		buffer.append(h).append("h "); //$NON-NLS-1$
513		long m = (time % Util.ONE_HOUR) / Util.ONE_MINUTE;
514		buffer.append(m).append("mn "); //$NON-NLS-1$
515		long seconds = m%Util.ONE_MINUTE;
516		buffer.append(seconds/1000);
517		buffer.append("s"); //$NON-NLS-1$
518	}
519	return buffer.toString();
520}
521
522private Util() {
523	// don't instantiate
524}
525
526/**
527 * Set the milestones.
528 *
529 * @param items The milestones list (e.g. {@link IPerformancesConstants#V35_MILESTONES}).
530 */
531public static void setMilestones(String[] items) {
532	MILESTONES = items;
533}
534
535/**
536 * Init the milestones from preferences
537 *
538 * @param preferences The preferences from which got milestones list
539 */
540public static void initMilestones(IEclipsePreferences preferences) {
541	int eclipseVersion = preferences.getInt(IPerformancesConstants.PRE_ECLIPSE_VERSION, IPerformancesConstants.DEFAULT_ECLIPSE_VERSION);
542	String prefix = IPerformancesConstants.PRE_MILESTONE_BUILDS + "." + eclipseVersion;
543	int index = 0;
544	String milestone = preferences.get(prefix + index, null);
545	String[] milestones = new String[20];
546	while (milestone != null) {
547		milestones[index] = milestone;
548		index++;
549		milestone = preferences.get(prefix + index, null);
550	}
551	int length = milestones.length;
552	if (index < length) {
553		System.arraycopy(milestones, 0, milestones = new String[index], 0, index);
554	}
555	MILESTONES = milestones;
556}
557}
558