Home | History | Annotate | Download | only in model
      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  *******************************************************************************/
     11 package org.eclipse.test.internal.performance.results.model;
     12 
     13 import java.util.ArrayList;
     14 import java.util.List;
     15 import java.util.Vector;
     16 
     17 import org.eclipse.test.internal.performance.results.db.*;
     18 import org.eclipse.test.internal.performance.results.utils.IPerformancesConstants;
     19 import org.eclipse.test.internal.performance.results.utils.Util;
     20 import org.eclipse.ui.views.properties.ComboBoxPropertyDescriptor;
     21 import org.eclipse.ui.views.properties.IPropertyDescriptor;
     22 import org.eclipse.ui.views.properties.PropertyDescriptor;
     23 import org.eclipse.ui.views.properties.TextPropertyDescriptor;
     24 
     25 public class ConfigResultsElement extends ResultsElement {
     26 
     27 	// Elements
     28 	BuildResultsElement currentBuild, baselineBuild;
     29 
     30 	// Property descriptors
     31 	static final String P_ID_CONFIG_NAME = "ConfigResultsElement.name"; //$NON-NLS-1$
     32 	static final String P_ID_CONFIG_DESCRIPTION = "ConfigResultsElement.description"; //$NON-NLS-1$
     33 	static final String P_ID_CONFIG_CURRENT_BUILD = "ConfigResultsElement.currentbuild"; //$NON-NLS-1$
     34 	static final String P_ID_CONFIG_BASELINE_BUILD = "ConfigResultsElement.baselinebuild"; //$NON-NLS-1$
     35 	static final String P_ID_CONFIG_BASELINED = "ConfigResultsElement.baselined"; //$NON-NLS-1$
     36 	static final String P_ID_CONFIG_VALID = "ConfigResultsElement.valid"; //$NON-NLS-1$
     37 	static final String P_ID_CONFIG_DELTA = "ConfigResultsElement.delta"; //$NON-NLS-1$
     38 	static final String P_ID_CONFIG_ERROR = "ConfigResultsElement.error"; //$NON-NLS-1$
     39 
     40 	static final String P_STR_CONFIG_NAME = "internal name"; //$NON-NLS-1$
     41 	static final String P_STR_CONFIG_DESCRIPTION = "description"; //$NON-NLS-1$
     42 	static final String P_STR_CONFIG_CURRENT_BUILD = "current build"; //$NON-NLS-1$
     43 	static final String P_STR_CONFIG_BASELINE_BUILD = "baseline build"; //$NON-NLS-1$
     44 	static final String P_STR_CONFIG_BASELINED = "has baseline"; //$NON-NLS-1$
     45 	static final String P_STR_CONFIG_VALID = "is valid"; //$NON-NLS-1$
     46 	static final String P_STR_CONFIG_DELTA = "delta with baseline"; //$NON-NLS-1$
     47 	static final String P_STR_CONFIG_ERROR = "delta error"; //$NON-NLS-1$
     48 
     49 	private static final TextPropertyDescriptor CONFIG_NAME_DESCRIPTOR = new TextPropertyDescriptor(P_ID_CONFIG_NAME, P_STR_CONFIG_NAME);
     50 	private static final TextPropertyDescriptor CONFIG_DESCRIPTION_DESCRIPTOR = new TextPropertyDescriptor(P_ID_CONFIG_DESCRIPTION, P_STR_CONFIG_DESCRIPTION);
     51 	private static final PropertyDescriptor CONFIG_CURRENT_BUILD_DESCRIPTOR = new PropertyDescriptor(P_ID_CONFIG_CURRENT_BUILD, P_STR_CONFIG_CURRENT_BUILD);
     52 	private static final PropertyDescriptor CONFIG_BASELINE_BUILD_DESCRIPTOR = new PropertyDescriptor(P_ID_CONFIG_BASELINE_BUILD, P_STR_CONFIG_BASELINE_BUILD);
     53 	private static final PropertyDescriptor CONFIG_BASELINED_DESCRIPTOR = new PropertyDescriptor(P_ID_CONFIG_BASELINED, P_STR_CONFIG_BASELINED);
     54 	private static final PropertyDescriptor CONFIG_VALID_DESCRIPTOR = new PropertyDescriptor(P_ID_CONFIG_VALID, P_STR_CONFIG_VALID);
     55 	private static final PropertyDescriptor CONFIG_DELTA_DESCRIPTOR = new PropertyDescriptor(P_ID_CONFIG_DELTA, P_STR_CONFIG_DELTA);
     56 	private static final PropertyDescriptor CONFIG_ERROR_DESCRIPTOR = new PropertyDescriptor(P_ID_CONFIG_ERROR, P_STR_CONFIG_ERROR);
     57 
     58     private static Vector DESCRIPTORS;
     59     static Vector initDescriptors(int status) {
     60 		DESCRIPTORS = new Vector();
     61 		// Status category
     62 		DESCRIPTORS.add(getInfosDescriptor(status));
     63 		DESCRIPTORS.add(getWarningsDescriptor(status));
     64 		DESCRIPTORS.add(ERROR_DESCRIPTOR);
     65 		ERROR_DESCRIPTOR.setCategory("Status");
     66 		// Results category
     67 		DESCRIPTORS.addElement(CONFIG_NAME_DESCRIPTOR);
     68 		CONFIG_NAME_DESCRIPTOR.setCategory("Results");
     69 		DESCRIPTORS.addElement(CONFIG_DESCRIPTION_DESCRIPTOR);
     70 		CONFIG_DESCRIPTION_DESCRIPTOR.setCategory("Results");
     71 		DESCRIPTORS.addElement(CONFIG_CURRENT_BUILD_DESCRIPTOR);
     72 		CONFIG_CURRENT_BUILD_DESCRIPTOR.setCategory("Results");
     73 		DESCRIPTORS.addElement(CONFIG_BASELINE_BUILD_DESCRIPTOR);
     74 		CONFIG_BASELINE_BUILD_DESCRIPTOR.setCategory("Results");
     75 		DESCRIPTORS.addElement(CONFIG_BASELINED_DESCRIPTOR);
     76 		CONFIG_BASELINED_DESCRIPTOR.setCategory("Results");
     77 		DESCRIPTORS.addElement(CONFIG_VALID_DESCRIPTOR);
     78 		CONFIG_VALID_DESCRIPTOR.setCategory("Results");
     79 		DESCRIPTORS.addElement(CONFIG_DELTA_DESCRIPTOR);
     80 		CONFIG_DELTA_DESCRIPTOR.setCategory("Results");
     81 		DESCRIPTORS.addElement(CONFIG_ERROR_DESCRIPTOR);
     82 		CONFIG_ERROR_DESCRIPTOR.setCategory("Results");
     83 		// Survey category
     84 		DESCRIPTORS.add(COMMENT_DESCRIPTOR);
     85 		COMMENT_DESCRIPTOR.setCategory("Survey");
     86 		return DESCRIPTORS;
     87 	}
     88     static ComboBoxPropertyDescriptor getInfosDescriptor(int status) {
     89 		List list = new ArrayList();
     90 		if ((status & SMALL_VALUE) != 0) {
     91 			list.add("This test and/or its variation has a small value on this machine, hence it may not be necessary to spend time on fixing it if a regression occurs");
     92 		}
     93 		if ((status & STUDENT_TTEST) != 0) {
     94 			list.add("The student-t test error on this machine is over the threshold");
     95 		}
     96 		String[] infos = new String[list.size()];
     97 		if (list.size() > 0) {
     98 			list.toArray(infos);
     99 		}
    100 		ComboBoxPropertyDescriptor infoDescriptor = new ComboBoxPropertyDescriptor(P_ID_STATUS_INFO, P_STR_STATUS_INFO, infos);
    101 		infoDescriptor.setCategory("Status");
    102 		return infoDescriptor;
    103 	}
    104     static PropertyDescriptor getWarningsDescriptor(int status) {
    105 		List list = new ArrayList();
    106 		if ((status & BIG_ERROR) != 0) {
    107 			list.add("The error on this machine is over the 3% threshold, hence its result may not be really reliable");
    108 		}
    109 		if ((status & NOT_RELIABLE) != 0) {
    110 			list.add("The results history for this machine shows that the variation of its delta is over 20%, hence its result is surely not reliable");
    111 		}
    112 		if ((status & NOT_STABLE) != 0) {
    113 			list.add("The results history for this machine shows that the variation of its delta is between 10% and 20%, hence its result may not be really reliable");
    114 		}
    115 		if ((status & NO_BASELINE) != 0) {
    116 			list.add("There's no baseline for this machine to compare with");
    117 		}
    118 		if ((status & SINGLE_RUN) != 0) {
    119 			list.add("This test has only one run on this machine, hence no error can be computed to verify if it's stable enough to be reliable");
    120 		}
    121 		if ((status & STUDENT_TTEST) != 0) {
    122 			list.add("The student-t test error on this machine is over the threshold");
    123 		}
    124 		String[] warnings = new String[list.size()];
    125 		if (list.size() > 0) {
    126 			list.toArray(warnings);
    127 		}
    128 		ComboBoxPropertyDescriptor warningDescriptor = new ComboBoxPropertyDescriptor(P_ID_STATUS_WARNING, P_STR_STATUS_WARNING, warnings);
    129 		warningDescriptor.setCategory("Status");
    130 		return warningDescriptor;
    131 	}
    132     static Vector getDescriptors() {
    133     	return DESCRIPTORS;
    134 	}
    135 
    136 public ConfigResultsElement(AbstractResults results, ResultsElement parent) {
    137 	super(results, parent);
    138 }
    139 
    140 public int compareTo(Object o) {
    141 	// TODO Auto-generated method stub
    142 	return super.compareTo(o);
    143 }
    144 ResultsElement createChild(AbstractResults testResults) {
    145 	return new BuildResultsElement(testResults, this);
    146 }
    147 
    148 BuildResultsElement getBaselineBuild() {
    149 	if (this.baselineBuild == null) {
    150 		this.baselineBuild = new BuildResultsElement(getConfigResults().getBaselineBuildResults(), this);
    151 	}
    152 	return this.baselineBuild;
    153 }
    154 
    155 /**
    156  * Get the baseline build used for this configuration.
    157  *
    158  * @param buildName The name of the build to have the baseline
    159  * @return The baseline build as {@link BuildResultsElement}.
    160  */
    161 public String getBaselineBuildName(String buildName) {
    162 	return getConfigResults().getBaselineBuildResults(buildName).getName();
    163 }
    164 
    165 private ConfigResults getConfigResults() {
    166 	return (ConfigResults) this.results;
    167 }
    168 
    169 BuildResultsElement getCurrentBuild() {
    170 	if (this.currentBuild == null) {
    171 		this.currentBuild = new BuildResultsElement(getConfigResults().getCurrentBuildResults(), this);
    172 	}
    173 	return this.currentBuild;
    174 }
    175 
    176 public String getLabel(Object o) {
    177 	String description = getConfigResults().getDescription();
    178 	int index = description.indexOf(" (");
    179 	if (index <= 0) {
    180 		return description;
    181 	}
    182 	return description.substring(0, index);
    183 }
    184 
    185 /*
    186  * (non-Javadoc)
    187  *
    188  * @see org.eclipse.ui.views.properties.IPropertySource#getPropertyDescriptors()
    189  */
    190 public IPropertyDescriptor[] getPropertyDescriptors() {
    191 	Vector descriptors = getDescriptors();
    192 	if (descriptors == null) {
    193 		descriptors = initDescriptors(getStatus());
    194 	}
    195 	int size = descriptors.size();
    196 	IPropertyDescriptor[] descriptorsArray = new IPropertyDescriptor[size];
    197 	descriptorsArray[0] = getInfosDescriptor(getStatus());
    198 	descriptorsArray[1] = getWarningsDescriptor(getStatus());
    199 	for (int i=2; i<size; i++) {
    200 		descriptorsArray[i] = (IPropertyDescriptor) descriptors.get(i);
    201 	}
    202 	return descriptorsArray;
    203 }
    204 
    205 /*
    206  * (non-Javadoc)
    207  *
    208  * @see
    209  * org.eclipse.ui.views.properties.IPropertySource#getPropertyValue(java.lang
    210  * .Object)
    211  */
    212 public Object getPropertyValue(Object propKey) {
    213 	ConfigResults configResults = getConfigResults();
    214 	if (propKey.equals(P_ID_CONFIG_NAME)) {
    215 		return configResults.getName();
    216 	}
    217 	if (propKey.equals(P_ID_CONFIG_DESCRIPTION)) {
    218 		return configResults.getDescription();
    219 	}
    220 	if (propKey.equals(P_ID_CONFIG_CURRENT_BUILD)) {
    221 		return getCurrentBuild();
    222 	}
    223 	if (propKey.equals(P_ID_CONFIG_BASELINE_BUILD)) {
    224 		return getBaselineBuild();
    225 	}
    226 	if (propKey.equals(P_ID_CONFIG_BASELINED)) {
    227 		return new Boolean(configResults.isBaselined());
    228 	}
    229 	if (propKey.equals(P_ID_CONFIG_VALID)) {
    230 		return new Boolean(configResults.isValid());
    231 	}
    232 	if (propKey.equals(P_ID_CONFIG_DELTA)) {
    233 		return new Double(configResults.getDelta());
    234 	}
    235 	if (propKey.equals(P_ID_CONFIG_ERROR)) {
    236 		return new Double(configResults.getError());
    237 	}
    238 	if (propKey.equals(P_ID_STATUS_ERROR)) {
    239 		if (getStatus() == MISSING) {
    240 			PerformanceResultsElement performanceResultsElement = (PerformanceResultsElement) ((ResultsElement)((ResultsElement)getParent(null)).getParent(null)).getParent(null);
    241 			return "No result for build "+performanceResultsElement.getName()+" on this machine!";
    242 		}
    243 		if ((getStatus() & BIG_DELTA) != 0) {
    244 			return "The delta on this machine is over the 10% threshold, hence may indicate a possible regression";
    245 		}
    246 	}
    247 	return super.getPropertyValue(propKey);
    248 }
    249 
    250 /**
    251  * Return the statistics of the build along its history.
    252  *
    253  * @return An array of double built as follows:
    254  * <ul>
    255  * <li>0:	numbers of values</li>
    256  * <li>1:	mean of values</li>
    257  * <li>2:	standard deviation of these values</li>
    258  * <li>3:	coefficient of variation of these values</li>
    259  * </ul>
    260  */
    261 public double[] getStatistics() {
    262 	if (this.statistics  == null) {
    263 		this.statistics = getConfigResults().getStatistics(Util.BASELINE_BUILD_PREFIXES);
    264 	}
    265 	return this.statistics;
    266 }
    267 
    268 void initStatus() {
    269 	ConfigResults configResults = getConfigResults();
    270 	if (configResults.isValid()) {
    271 		initStatus(configResults.getCurrentBuildResults());
    272 	} else {
    273 		this.status = MISSING;
    274 	}
    275 }
    276 
    277 /*
    278  * Write the element status in the given stream
    279  */
    280 StringBuffer writableStatus(StringBuffer buffer, int kind, StringBuffer excluded) {
    281 	if ((this.status & BIG_DELTA) != 0) { // there's a failure on this config
    282 
    283 		// Get numbers
    284 		int buildsNumber = kind & IPerformancesConstants.STATUS_BUILDS_NUMBER_MASK;
    285 		ConfigResults configResults = getConfigResults();
    286 		double[][] numbers = configResults.getLastNumbers(buildsNumber);
    287 		int numbersLength = numbers.length;
    288 
    289 		// if there are several builds to confirm the regression, then verify all deltas
    290 		if (numbersLength > 1) {
    291 			if (numbersLength < buildsNumber) {
    292 				// there's not enough builds to wee whether there's a real regression, hence skip result
    293 				if (excluded != null) {
    294 					excluded.append(configResults+" excluded from status because there's only "+numbersLength+" builds available although "+buildsNumber+" is required to decide a regression is confirmed or not!");
    295 					excluded.append(Util.LINE_SEPARATOR);
    296 				}
    297 				return buffer;
    298 			}
    299 			int confirmed = 1;
    300 			for (int i=1; i<numbersLength; i++) {
    301 				if (numbers[i][AbstractResults.DELTA_VALUE_INDEX] < -0.1) {
    302 					confirmed++;
    303 				}
    304 			}
    305 			float ratio = ((float) confirmed) / numbersLength;
    306 			if (ratio < 0.8) {
    307 				// more than 20% of previous build didn't fail, hence skip result
    308 				if (excluded != null) {
    309 					excluded.append(configResults+" excluded from status because only "+confirmed+" builds failed on last "+buildsNumber+" ones!");
    310 					excluded.append(Util.LINE_SEPARATOR);
    311 				}
    312 				return buffer;
    313 			}
    314 		}
    315 
    316 		// Add values
    317 		double[] values = numbers[0];
    318 		double buildValue = values[AbstractResults.BUILD_VALUE_INDEX];
    319 		double baselineValue = values[AbstractResults.BASELINE_VALUE_INDEX];
    320 		double delta = values[AbstractResults.DELTA_VALUE_INDEX];
    321 		double error = values[AbstractResults.DELTA_ERROR_INDEX];
    322 		StringBuffer localBuffer = new StringBuffer("		");
    323 		localBuffer.append(configResults.getName());
    324 		double[] stats = null;
    325 		boolean printValues = (kind & IPerformancesConstants.STATUS_VALUES) != 0;
    326 		if (printValues) {
    327 			localBuffer.append("	");
    328 			localBuffer.append(buildValue);
    329 			localBuffer.append("	");
    330 			localBuffer.append(baselineValue);
    331 			localBuffer.append("	");
    332 			localBuffer.append(buildValue-baselineValue);
    333 			localBuffer.append("	");
    334 			localBuffer.append(Util.PERCENTAGE_FORMAT.format(delta));
    335 			localBuffer.append("	");
    336 			localBuffer.append(Util.PERCENTAGE_FORMAT.format(error));
    337 			stats = getStatistics();
    338 			if (stats != null) {
    339 				localBuffer.append("	");
    340 				localBuffer.append((int) stats[0]);
    341 				localBuffer.append("	");
    342 				localBuffer.append(Util.DOUBLE_FORMAT.format(stats[1]));
    343 				localBuffer.append("	");
    344 				localBuffer.append(Util.DOUBLE_FORMAT.format(stats[2]));
    345 				localBuffer.append("	");
    346 				localBuffer.append(Util.PERCENTAGE_FORMAT.format(stats[3]));
    347 			}
    348 		}
    349 
    350 		/* Add comment
    351 		IEclipsePreferences preferences = new InstanceScope().getNode(IPerformancesConstants.PLUGIN_ID);
    352 		String comment = preferences.get(getId(), null);
    353 		if (comment != null) {
    354 			if (stats == null && printValues) {
    355 				buffer.append("				");
    356 			}
    357 			buffer.append("	");
    358 			buffer.append(comment);
    359 		}
    360 		*/
    361 
    362 		// Add status info
    363 		if (this.status != BIG_DELTA) { // there's some other info in the status
    364 //			if (comment == null) {
    365 				if (stats == null && printValues) {
    366 					localBuffer.append("				");
    367 				}
    368 //			}
    369 			localBuffer.append("	");
    370 			String separator = "";
    371 
    372 			// Error
    373 			if ((this.status & BIG_ERROR) != 0) {
    374 				int statusErrorLevel = kind & IPerformancesConstants.STATUS_ERROR_LEVEL_MASK;
    375 				if (statusErrorLevel == IPerformancesConstants.STATUS_ERROR_NOTICEABLE) {
    376 					// Skip result
    377 					if (excluded != null) {
    378 						excluded.append(configResults+" excluded from status due to a noticeable error!");
    379 						excluded.append(Util.LINE_SEPARATOR);
    380 					}
    381 					return buffer;
    382 				}
    383 				localBuffer.append(separator);
    384 				localBuffer.append("error (");
    385 				localBuffer.append(Util.PERCENTAGE_FORMAT.format(error));
    386 				localBuffer.append(")");
    387 				separator = "+";
    388 				double ratio = -(error/delta);
    389 				if (ratio > 1) {
    390 					switch (statusErrorLevel) {
    391 						case IPerformancesConstants.STATUS_ERROR_INVALID:
    392 						case IPerformancesConstants.STATUS_ERROR_WEIRD:
    393 						case IPerformancesConstants.STATUS_ERROR_SUSPICIOUS:
    394 							// Skip result
    395 							if (excluded != null) {
    396 								excluded.append(configResults+" excluded from status due to an invalid error!");
    397 								excluded.append(Util.LINE_SEPARATOR);
    398 							}
    399 							return buffer;
    400 					}
    401 					localBuffer.append(": invalid measure!");
    402 				} else if (ratio > 0.5) {
    403 					switch (statusErrorLevel) {
    404 						case IPerformancesConstants.STATUS_ERROR_WEIRD:
    405 						case IPerformancesConstants.STATUS_ERROR_SUSPICIOUS:
    406 							// Skip result
    407 							if (excluded != null) {
    408 								excluded.append(configResults+" excluded from status due to a weird error!");
    409 								excluded.append(Util.LINE_SEPARATOR);
    410 							}
    411 							return buffer;
    412 					}
    413 					localBuffer.append(": weird measure!");
    414 				} else if (ratio > 0.25) {
    415 					if (statusErrorLevel == IPerformancesConstants.STATUS_ERROR_SUSPICIOUS) {
    416 						// Skip result
    417 						if (excluded != null) {
    418 							excluded.append(configResults+" excluded from status due to a suspicious error!");
    419 							excluded.append(Util.LINE_SEPARATOR);
    420 						}
    421 						return buffer;
    422 					}
    423 					localBuffer.append(": suspicious measure!");
    424 				}
    425 			}
    426 
    427 			// Small value
    428 			if ((this.status & SMALL_VALUE) != 0) {
    429 				int statusSmallValue = kind & IPerformancesConstants.STATUS_SMALL_VALUE_MASK;
    430 				localBuffer.append(separator);
    431 				if (buildValue < 100) {
    432 					if (statusSmallValue == IPerformancesConstants.STATUS_SMALL_VALUE_BUILD) {
    433 						// Skip result
    434 						if (excluded != null) {
    435 							excluded.append(configResults+" excluded from status due to a small build value!");
    436 							excluded.append(Util.LINE_SEPARATOR);
    437 						}
    438 						return buffer;
    439 					}
    440 					localBuffer.append("small build value (");
    441 					localBuffer.append((int)buildValue);
    442 					localBuffer.append("ms)");
    443 				}
    444 				int diff = (int) Math.abs(baselineValue - buildValue);
    445 				if (diff < 100) {
    446 					if (statusSmallValue == IPerformancesConstants.STATUS_SMALL_VALUE_DELTA) {
    447 						// Skip result
    448 						if (excluded != null) {
    449 							excluded.append(configResults+" excluded from status due to a small delta value!");
    450 							excluded.append(Util.LINE_SEPARATOR);
    451 						}
    452 						return buffer;
    453 					}
    454 					localBuffer.append("small delta value (");
    455 					localBuffer.append(diff);
    456 					localBuffer.append("ms)");
    457 				}
    458 				separator = "+";
    459 			}
    460 
    461 			// Statistics
    462 			if ((this.status & NOT_RELIABLE) != 0) {
    463 				switch (kind & IPerformancesConstants.STATUS_STATISTICS_MASK) {
    464 					case IPerformancesConstants.STATUS_STATISTICS_UNSTABLE:
    465 					case IPerformancesConstants.STATUS_STATISTICS_ERRATIC:
    466 						// Skip result
    467 						if (excluded != null) {
    468 							excluded.append(configResults+" excluded from status due to erratic statistics!");
    469 							excluded.append(Util.LINE_SEPARATOR);
    470 						}
    471 						return buffer;
    472 				}
    473 				localBuffer.append(separator);
    474 				localBuffer.append("erratic");
    475 				separator = "+";
    476 			} else if ((this.status & NOT_STABLE) != 0) {
    477 				if ((kind & IPerformancesConstants.STATUS_STATISTICS_UNSTABLE) != 0) {
    478 					// Skip result
    479 					if (excluded != null) {
    480 						excluded.append(configResults+" excluded from status due to unstable statistics!");
    481 						excluded.append(Util.LINE_SEPARATOR);
    482 					}
    483 					return buffer;
    484 				}
    485 				localBuffer.append(separator);
    486 				localBuffer.append("unstable");
    487 				separator = "+";
    488 			}
    489 		}
    490 
    491 		// Write status
    492 		buffer.append(localBuffer);
    493 		buffer.append(Util.LINE_SEPARATOR);
    494 	}
    495 	return buffer;
    496 }
    497 
    498 }
    499