tracesrv/tracecompiler/src/com.nokia.tracecompiler/src/com/nokia/tracecompiler/engine/TraceLocationConverter.java
changeset 56 aa2539c91954
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tracesrv/tracecompiler/src/com.nokia.tracecompiler/src/com/nokia/tracecompiler/engine/TraceLocationConverter.java	Fri Oct 08 14:56:39 2010 +0300
@@ -0,0 +1,679 @@
+/*
+ * Copyright (c) 2008-2010 Nokia Corporation and/or its subsidiary(-ies). 
+ * All rights reserved.
+ * This component and the accompanying materials are made available
+ * under the terms of "Eclipse Public License v1.0"
+ * which accompanies this distribution, and is available
+ * at the URL "http://www.eclipse.org/legal/epl-v10.html".
+ *
+ * Initial Contributors:
+ * Nokia Corporation - initial contribution.
+ *
+ * Contributors:
+ *
+ * Description:
+ *
+ * Location converter monitors locations and converts them to traces if necessary.
+ *
+ */
+package com.nokia.tracecompiler.engine;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Properties;
+
+import com.nokia.tracecompiler.TraceCompilerConstants;
+import com.nokia.tracecompiler.TraceCompilerLogger;
+import com.nokia.tracecompiler.engine.TraceCompilerEngineErrorCodes.TraceCompilerErrorCode;
+import com.nokia.tracecompiler.engine.project.ProjectConstants;
+import com.nokia.tracecompiler.engine.project.ProjectEngine;
+import com.nokia.tracecompiler.engine.project.SortedProperties;
+import com.nokia.tracecompiler.engine.project.TraceIDCache;
+import com.nokia.tracecompiler.engine.rules.PerformanceEventRuleBase;
+import com.nokia.tracecompiler.engine.rules.StateTraceRule;
+import com.nokia.tracecompiler.engine.source.SourceParserRule;
+import com.nokia.tracecompiler.engine.source.SourceProperties;
+import com.nokia.tracecompiler.engine.source.SourceParserRule.ParameterConversionResult;
+import com.nokia.tracecompiler.engine.source.SourceParserRule.TraceConversionResult;
+import com.nokia.tracecompiler.model.Trace;
+import com.nokia.tracecompiler.model.TraceCompilerException;
+import com.nokia.tracecompiler.model.TraceGroup;
+import com.nokia.tracecompiler.model.TraceModel;
+import com.nokia.tracecompiler.model.TraceModelExtension;
+import com.nokia.tracecompiler.model.TraceModelPersistentExtension;
+import com.nokia.tracecompiler.model.TraceParameter;
+import com.nokia.tracecompiler.project.FormattingUtils;
+import com.nokia.tracecompiler.project.GroupNames;
+import com.nokia.tracecompiler.project.ProjectUtils;
+import com.nokia.tracecompiler.source.SourceConstants;
+
+/**
+ * Location converter monitors locations and converts them to traces if
+ * necessary.
+ * 
+ */
+public final class TraceLocationConverter {
+
+	/**
+	 * Number of deprecated groups
+	 */
+	final static int NUMBER_OF_DEPRECATED_GROUPS = 3;
+
+	/**
+	 * Deprecated group name map
+	 */
+	Map<String, String> deprecatedGroupNameMap = new HashMap<String, String>(
+			NUMBER_OF_DEPRECATED_GROUPS);
+
+	/**
+	 * Trace model
+	 */
+	private TraceModel model;
+
+	/**
+	 * Constructor
+	 * 
+	 * @param model
+	 *            the trace model
+	 */
+	TraceLocationConverter(TraceModel model) {
+		this.model = model;
+		initializeDeprecatedGroupNameMap();
+	}
+
+	/**
+	 * Initialize deprecated group name map
+	 */
+	private void initializeDeprecatedGroupNameMap() {
+		deprecatedGroupNameMap.put(GroupNames.TRACE_API,
+				GroupNames.TRACE_BORDER);
+		deprecatedGroupNameMap.put(GroupNames.TRACE_DEBUG,
+				GroupNames.TRACE_DUMP);
+		deprecatedGroupNameMap.put(GroupNames.TRACE_DETAILED,
+				GroupNames.TRACE_INTERNALS);
+	}
+
+	/**
+	 * Source opened notification
+	 * 
+	 * @param properties
+	 *            the source properties
+	 * @throws TraceCompilerException 
+	 */
+	void sourceOpened(SourceProperties properties) throws TraceCompilerException {
+		//make sure all errors are reported AFTER processing a file
+		TraceCompilerEngineGlobals.getSourceContextManager()
+				.setConverting(true);
+		model.startProcessing();
+		try {
+			// Load fixed group and trace ids from definition file to model
+			//no errors are reported here 
+			if (model.getFixedIds() == null) {
+				loadFixedIdsFromDefinitionFileToModel();
+			}
+
+			for (TraceLocation loc : properties) {
+				autoConvertLocation(loc);
+			}
+			// If there are duplicates or unused traces, they are removed
+			// Note that this will work across source files although this
+			// function is processing only one file.
+			// If a trace is created, all locations from all open source files
+			// are linked to that trace and thus it will be removed as
+			// duplicate.
+			removeDuplicateTraces();
+
+		} finally {
+			model.processingComplete();
+			SourceContextManager manager = TraceCompilerEngineGlobals
+					.getSourceContextManager();
+			manager.setConverting(false);
+			manager.setContext(null);
+		}
+	}
+
+	/**
+	 * Load fixed Ids from definiton file to properties
+	 * 
+	 */
+	private void loadFixedIdsFromDefinitionFileToModel() {
+		TraceIDCache cache = model.getExtension(TraceIDCache.class);
+
+		// Create trace Id cache if it does not exist
+		try {
+			if (cache == null) {
+				String path;
+				path = ProjectUtils.getLocationForFile(model,
+						ProjectEngine.traceFolderName,
+						ProjectConstants.FIXED_ID_DEFINITIONS_FILE_NAME, false);
+				if (path != null) {
+					cache = new TraceIDCache(new File(path).getParent());
+					model.addExtension(cache);
+				}
+			}
+			if (cache != null) {
+				File cacheFile = new File(cache.getAbsolutePath());
+				// Try to load Ids from trace Id cache file
+				try {
+					SortedProperties fixedIds = new SortedProperties();
+					if (cacheFile.exists()) {
+						String thisLine;
+						boolean allOldFixedIdsAreValid = true;
+						String group = model.GROUP_PROPERTY_PREFIX;
+						FileInputStream fis = new FileInputStream(cacheFile);
+						BufferedReader myInput = new BufferedReader(
+								new InputStreamReader(fis));
+						// Read the old fixed id definition file
+						while ((thisLine = myInput.readLine()) != null) {
+							if (thisLine.indexOf(group) != -1) {
+								thisLine = thisLine.substring(group.length(),
+										thisLine.length());
+								if (!searchForOldGroupIds(thisLine)) {						
+									String msg  = Messages.getString("TraceLocationConverter.GroupIdValueError"); //$NON-NLS-1$
+									TraceCompilerEngineGlobals.getEvents().postWarningMessage(msg, null);
+									// We need only one old invalid id to make all of them invalid
+									allOldFixedIdsAreValid = false;
+									break;
+								}
+							}
+						}
+						fis.close();
+						
+						if (allOldFixedIdsAreValid) {
+							// Create file input stream again
+							FileInputStream newFis = new FileInputStream(cacheFile);
+							fixedIds.load(newFis);
+							// Load fixed Ids from properties to model
+							model.setFixedIds(fixedIds);
+							newFis.close();
+						}
+						
+					}
+				} catch (IOException e) {
+					// If there is a problem with the cache file, just delete it
+					// make sure the model fixed ids is null
+					model.setFixedIds(null);
+					cacheFile.delete();
+					// We do not raise an exception but we need to report a info
+					TraceCompilerLogger.printWarning(Messages.getString("TraceLocationConverter.FixedIdProblemWarningBeginText") + cacheFile.toString() + Messages.getString("TraceLocationConverter.FixedIdProblemWarningMiddleText") + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
+				}
+			}
+		} catch (TraceCompilerException e) {
+			// This exception can probably be ignored
+			TraceCompilerLogger.printWarning("Could not create ID cache : " + e.getMessage()); //$NON-NLS-1$
+		}
+	}
+
+	/**
+	 * Searches the fixed_id file to see if there are any old definitions
+	 * 
+	 * @param string
+	 *            of text from the fixed_id files
+	 * @return boolean 
+	 */
+	public static boolean searchForOldGroupIds(String str) {
+		
+		boolean oldGroupIdValid = true;
+
+		//get group name from the string
+		int index = str.indexOf(SourceConstants.ASSIGN_WITH_OUT_SPACES);
+		if (index == -1) {
+			//not valid string, so ignore it, just return success
+			return true;
+		}
+
+		String groupName = str.substring(0, index);
+
+		//get group id
+		str = str.substring(str.indexOf(SourceConstants.HEX_PREFIX) + 2, str
+				.length());
+
+		int groupId = -1;
+
+		try {
+			groupId = Integer.parseInt(str.trim(),
+					TraceCompilerConstants.HEX_RADIX);
+		} catch (NumberFormatException e) {
+			//it's not an hex number so fail
+			oldGroupIdValid = false;
+		}
+
+		//get group id from ost header file
+		int fileGroupId = GroupNames.getIdByName(groupName);
+
+		if (fileGroupId != 0) {
+			//found 1
+			if (fileGroupId != groupId) //group id has changed, so old one can't be used
+				oldGroupIdValid = false;
+		} else {
+			if ((GroupNames.USER_GROUP_ID_FIRST > groupId)
+					|| (groupId > GroupNames.USER_GROUP_ID_LAST)) //not within new user defined Ids
+				oldGroupIdValid = false;
+		}
+
+		return oldGroupIdValid;
+	}
+
+	/**
+	 * Converts the given location to trace if parser supports auto-conversion
+	 * 
+	 * @param location
+	 *            the location
+	 */
+	private void autoConvertLocation(TraceLocation location) {
+		// Stores the context of the location to the context manager.
+		TraceCompilerEngineGlobals.getSourceContextManager().setContext(
+				location.getParser().getContext(location.getOffset()));
+		Trace trace = location.getTrace();
+		if (trace == null) {
+			// If the trace does not exist, the parser determines if the
+			// location can be converted
+			if (location.getParserRule().getLocationParser()
+					.isLocationConverted(location)) {
+				try {
+					convertLocation(location, null, true);
+				} catch (TraceCompilerException e) {
+					// If converter fails, the error code is stored into the
+					// location. The location notifies all validity listeners
+					// about the change
+					location.setConverterErrorCode((TraceCompilerErrorCode) e
+							.getErrorCode(), e.getErrorParameters());
+				}
+			}
+		} else {
+			// If the trace already exists in the model, it is updated
+			// based on the source file contents
+			updateLocation(location);
+		}
+	}
+
+	/**
+	 * Recreates the trace from changed location when source is saved
+	 * 
+	 * @param location
+	 *            the location to be checked
+	 */
+	private void updateLocation(TraceLocation location) {
+		// Parser determines if the location can be converted
+		if (location.getParserRule().getLocationParser().isLocationConverted(
+				location)) {
+			try {
+				Trace trace = location.getTrace();
+				// If a location has changed, the old trace is removed
+				// and a new one created. Persistent extensions are moved to the
+				// new trace
+				Iterator<TraceModelPersistentExtension> extensions = null;
+				if (trace != null) {
+					extensions = trace
+							.getExtensions(TraceModelPersistentExtension.class);
+					trace.getGroup().removeTrace(trace);
+				}
+				convertLocation(location, extensions, true);
+
+				// Check that the location is inside a function. Otherwise throw
+				// an error because the code is unreachable
+				if (location.getFunctionName() == null) {
+					throw new TraceCompilerException(
+							TraceCompilerErrorCode.UNREACHABLE_TRACE_LOCATION);
+				}
+
+			} catch (TraceCompilerException e) {
+				// If converter fails, the error code is stored into the
+				// location. The location notifies all validity listeners about
+				// the change
+				location.setConverterErrorCode((TraceCompilerErrorCode) e
+						.getErrorCode(), e.getErrorParameters());
+			}
+		}
+	}
+
+	/**
+	 * Removes all duplicate traces from the model
+	 * @throws TraceCompilerException 
+	 */
+	private void removeDuplicateTraces() throws TraceCompilerException {
+		boolean groupRemoved = true;
+		while (groupRemoved) {
+			groupRemoved = false;
+			for (TraceGroup group : model) {
+				removeDuplicateTracesFromGroup(group);
+				if (!group.hasTraces()) {
+					model.removeGroup(group);
+					groupRemoved = true;
+					break;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Removes duplicate traces from a trace group
+	 * 
+	 * @param group
+	 *            the group
+	 * @throws TraceCompilerException 
+	 */
+	private void removeDuplicateTracesFromGroup(TraceGroup group) throws TraceCompilerException {
+		boolean traceRemoved = true;
+		while (traceRemoved) {
+			traceRemoved = false;
+			for (Trace trace : group) {
+				TraceLocationList list = trace
+						.getExtension(TraceLocationList.class);
+				if (list != null) {
+					if (list.getLocationCount() > 1) {
+						// All the locations are marked as duplicates and the
+						// trace is deleted
+						TraceCompilerErrorCode code = TraceCompilerErrorCode.TRACE_HAS_MULTIPLE_LOCATIONS;
+						for (LocationProperties loc : list) {
+							((TraceLocation) loc).setConverterErrorCode(code,
+									null);
+						}
+						group.removeTrace(trace);
+						traceRemoved = true;
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Converts a location to a Trace object.
+	 * 
+	 * @param location
+	 *            the location to be converted
+	 * @param extensions
+	 *            persistent extensions to be added to the new trace
+	 * @param autoConvert
+	 *            true if converting without user interaction
+	 * @return the new trace
+	 * @throws TraceCompilerException
+	 *             if conversion fails
+	 */
+	private Trace convertLocation(TraceLocation location,
+			Iterator<TraceModelPersistentExtension> extensions,
+			boolean autoConvert) throws TraceCompilerException {
+		Trace trace = null;
+		// If the parser has failed, the validity code is not OK and the
+		// location cannot be converted. Traces marked with no-trace error code
+		// have not yet been converted, so that is OK. Traces that have
+		// duplicate ID's error code can be parsed, since the duplicates might
+		// no longer exist.
+		if (!autoConvert
+				|| location.getValidityCode() == TraceCompilerErrorCode.OK
+				|| location.getValidityCode() == TraceCompilerErrorCode.TRACE_DOES_NOT_EXIST
+				|| location.getValidityCode() == TraceCompilerErrorCode.TRACE_HAS_MULTIPLE_LOCATIONS) {
+			// The parser does the actual conversion
+			SourceParserRule rule = location.getParserRule();
+			TraceConversionResult result = rule.getLocationParser()
+					.convertLocation(location);
+			// After parser has finished, the trace is created.
+			trace = convertWithoutUI(result, extensions);
+
+			if (trace != null) {
+				model.startProcessing();
+				try {
+					createParametersFromConversionResult(location, result,
+							trace);
+					// Runs a location validity check and notifies listeners
+					// that location is now OK
+					location.setConverterErrorCode(TraceCompilerErrorCode.OK,
+							null);
+				} catch (TraceCompilerException e) {
+					// If parameters cannot be created, the trace is removed
+					TraceGroup group = trace.getGroup();
+					trace.getGroup().removeTrace(trace);
+					if (!group.hasTraces()) {
+						group.getModel().removeGroup(group);
+					}
+					throw e;
+				} finally {
+					model.processingComplete();
+				}
+			}
+		}
+		return trace;
+	}
+
+	/**
+	 * Converts a location to trace without UI
+	 * 
+	 * @param result
+	 *            the conversion result from parser
+	 * @param extensions
+	 *            persistent extensions to be added to the new trace
+	 * @return the converted trace
+	 * @throws TraceCompilerException
+	 *             if location properties are not valid
+	 */
+	private Trace convertWithoutUI(TraceConversionResult result,
+			Iterator<TraceModelPersistentExtension> extensions)
+			throws TraceCompilerException {
+		Trace trace = null;
+		if (result.group != null) {
+			String groupName = result.group;
+			TraceGroup group = handleGroup(groupName);
+			trace = handleTrace(result, extensions, group);
+		} else {
+			throw new TraceCompilerException(
+					TraceCompilerErrorCode.GROUP_NOT_SELECTED);
+		}
+		return trace;
+	}
+
+	/**
+	 * Handle trace
+	 * 
+	 * @param result
+	 *            the conversion result from parser
+	 * @param extensions
+	 *            persistent extensions to be added to the new trace
+	 * @param group
+	 *            the group where trace belongs to
+	 * @return the trace
+	 * @throws TraceCompilerException
+	 */
+	private Trace handleTrace(TraceConversionResult result,
+			Iterator<TraceModelPersistentExtension> extensions, TraceGroup group)
+			throws TraceCompilerException {
+		// First try to find Id to trace from fixed Ids
+		Trace trace = null;
+		Properties fixedIds = model.getFixedIds();
+		String groupName = result.group;
+		String traceName = result.name;
+		int groupId = group.getID();
+		String groupIdString = SourceConstants.HEX_PREFIX
+				+ Integer.toString(groupId, model.HEX_RADIX).toUpperCase();
+		int traceId = 0;
+		if (fixedIds != null) {
+			String tracePropertyName = groupName + model.GROUP_ID_PREFIX
+					+ groupIdString + model.GROUP_ID_SUFFIX
+					+ SourceConstants.UNDERSCORE + traceName;
+			String value = fixedIds.getProperty(model.TRACE_PROPERTY_PREFIX
+					+ tracePropertyName);
+			if (value != null) {
+				try {
+					traceId = Integer.decode(value).intValue();
+				} catch (NumberFormatException e) {
+					// Corrupted, assign a proper Id later on
+					traceId = 0;
+				}
+			}
+		}
+		// If there was no fixed Id to this trace, get Id from model
+		if (traceId == 0) {
+			traceId = model.getNextTraceId(group);
+		}
+
+		String text = result.text;
+		model.getVerifier().checkTraceProperties(group, null, traceId,
+				traceName, text);
+		TraceModelExtension[] extArray = createExtensionArray(result,
+				extensions);
+		trace = model.getFactory().createTrace(group, traceId, traceName, text,
+				extArray);
+
+		if ((trace.getExtension(StateTraceRule.class) == null)
+				&& (group.getName()
+						.equals("TRACE_STATE"))) { //$NON-NLS-1$
+			throw new TraceCompilerException(
+					TraceCompilerErrorCode.INVALID_USAGE_OF_TRACE_STATE_GROUP_NAME);
+		} else if ((trace.getExtension(PerformanceEventRuleBase.class) == null)
+				&& (group.getName()
+						.equals("TRACE_PERFORMANCE"))) { //$NON-NLS-1$
+			throw new TraceCompilerException(
+					TraceCompilerErrorCode.INVALID_USAGE_OF_TRACE_PERFORMACE_GROUP_NAME);
+		}
+
+		return trace;
+	}
+
+	/**
+	 * Handle group. Try to fnd group from model. If it does not exist then
+	 * create new group.
+	 * 
+	 * @param groupName
+	 *            the name of the group
+	 * @return the handled group
+	 * @throws TraceCompilerException
+	 */
+	private TraceGroup handleGroup(String groupName)
+			throws TraceCompilerException {
+		String deprecatedGroupName = null;
+		
+		// Convert deprecated group name to valid group name if needed
+		if (deprecatedGroupNameMap.containsKey(groupName)) {
+			deprecatedGroupName = groupName;
+			groupName = deprecatedGroupNameMap.get(groupName);
+		}
+
+		// If auto-convert flag is set, the location is converted without
+		// user interaction. A new trace group is created if not found
+		TraceGroup group = model.findGroupByName(groupName);
+		if (group == null) {
+			
+			// Print warning related to deprecated group name change if needed
+			if (deprecatedGroupName != null) {
+				String message = Messages
+						.getString("TraceLocationConverter.DeprecatedGroupIdWarningStart");//$NON-NLS-1$
+				message += SourceConstants.SPACE + deprecatedGroupName
+						+ SourceConstants.SPACE;
+				message += Messages
+						.getString("TraceLocationConverter.DeprecatedGroupIdWarningMiddle");//$NON-NLS-1$
+				message += SourceConstants.SPACE + groupName
+						+ SourceConstants.SPACE;
+				message += Messages
+						.getString("TraceLocationConverter.DeprecatedGroupIdWarningEnd");//$NON-NLS-1$
+				TraceCompilerLogger.printWarning(message);
+			}
+
+			int groupId = 0;
+			Properties fixedIds = model.getFixedIds();
+
+			// First try to find Id to group from fixed Ids
+			if (fixedIds != null) {
+				String value = fixedIds.getProperty(model.GROUP_PROPERTY_PREFIX
+						+ groupName);
+				if (value != null) {
+					try {
+						groupId = Integer.decode(value).intValue();
+					} catch (NumberFormatException e) {
+						// Corrupted, assign a proper Id later on
+						groupId = 0;
+					}
+				}
+			}
+			// If there was no fixed Id to this group, get Id from model
+			if (groupId == 0) {
+				groupId = FormattingUtils.getGroupID(model, groupName);
+			}
+			model.getVerifier().checkTraceGroupProperties(model, null, groupId,
+					groupName);
+			group = model.getFactory().createTraceGroup(groupId, groupName,
+					null);
+		}
+
+		return group;
+	}
+
+	/**
+	 * Combines extensions into one array
+	 * 
+	 * @param result
+	 *            the conversion result
+	 * @param extensions
+	 *            the persistent extensions from old trace
+	 * @return the combined array of extensions
+	 */
+	private TraceModelExtension[] createExtensionArray(
+			TraceConversionResult result,
+			Iterator<TraceModelPersistentExtension> extensions) {
+		TraceModelExtension[] extArray = null;
+		ArrayList<TraceModelExtension> ext = null;
+		if (result.extensions != null) {
+			ext = new ArrayList<TraceModelExtension>();
+			ext.addAll(result.extensions);
+		}
+		if (extensions != null) {
+			if (ext == null) {
+				ext = new ArrayList<TraceModelExtension>();
+			}
+			while (extensions.hasNext()) {
+				ext.add(extensions.next());
+			}
+		}
+		if (ext != null) {
+			extArray = new TraceModelExtension[ext.size()];
+			ext.toArray(extArray);
+		}
+		return extArray;
+	}
+
+	/**
+	 * Creates the trace parameters based on trace conversion result
+	 * 
+	 * @param converted
+	 *            the location that was converted
+	 * @param result
+	 *            the conversion result
+	 * @param trace
+	 *            the trace
+	 * @throws TraceCompilerException
+	 *             if parameters cannot be created
+	 */
+	private void createParametersFromConversionResult(TraceLocation converted,
+			TraceConversionResult result, Trace trace)
+			throws TraceCompilerException {
+		if (result.parameters != null) {
+			for (int i = 0; i < result.parameters.size(); i++) {
+				int id = trace.getNextParameterID();
+				ParameterConversionResult res = result.parameters.get(i);
+				boolean warning = false;
+				if (res.type == null) {
+					warning = true;
+					res.type = TraceParameter.HEX32;
+				}
+				model.getVerifier().checkTraceParameterProperties(trace, null,
+						id, res.name, res.type);
+				TraceModelExtension[] extArray = null;
+				if (res.extensions != null) {
+					extArray = new TraceModelExtension[res.extensions.size()];
+					res.extensions.toArray(extArray);
+				}
+				TraceParameter param = model.getFactory().createTraceParameter(
+						trace, id, res.name, res.type, extArray);
+				if (warning) {
+					String msg = Messages
+							.getString("TraceCompiler.UnknownTypeWarning"); //$NON-NLS-1$
+					TraceCompilerEngineGlobals.getEvents().postWarningMessage(
+							msg, param);
+				}
+			}
+		}
+	}
+
+}