trace/tracebuilder/com.nokia.tracebuilder/src/com/nokia/tracebuilder/engine/TraceLocationMap.java
author Matti Laitinen <matti.t.laitinen@nokia.com>
Wed, 23 Jun 2010 14:35:40 +0300
changeset 10 ed1c9f64298a
permissions -rw-r--r--
TraceBuilder 2.4.0

/*
* Copyright (c) 2008 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:
*
* Maps trace locations into traces and vice versa
*
*/
package com.nokia.tracebuilder.engine;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import com.nokia.tracebuilder.engine.source.SourceProperties;
import com.nokia.tracebuilder.model.Trace;
import com.nokia.tracebuilder.model.TraceGroup;
import com.nokia.tracebuilder.model.TraceModel;
import com.nokia.tracebuilder.model.TraceObject;
import com.nokia.tracebuilder.source.SourceContext;

/**
 * Maps trace locations into traces and vice versa.
 * 
 */
public final class TraceLocationMap {

	/**
	 * List of unrelated traces
	 */
	private TraceLocationList unrelated = new TraceLocationList();

	/**
	 * Parser groups
	 */
	private HashMap<String, TraceLocationList> parserGroups = new HashMap<String, TraceLocationList>();

	/**
	 * The trace model
	 */
	private TraceModel model;

	/**
	 * Global list of locations, used for verification purposes with
	 * GLOBAL_LOCATION_ASSERTS configuration flag
	 */
	private ArrayList<TraceLocation> globalList;

	/**
	 * Flag which is set when "Source missing" error is shown. Prevents it to be
	 * shown again
	 */
	private boolean missingSourceErrorShown;

	/**
	 * Creates a location mapper
	 * 
	 * @param model
	 *            the trace model
	 */
	public TraceLocationMap(TraceModel model) {
		if (TraceBuilderConfiguration.GLOBAL_LOCATION_ASSERTS) {
			globalList = new ArrayList<TraceLocation>();
		}
		this.model = model;
		model.addModelListener(new LocationMapModelListener(this));
		model.addExtension(unrelated);
	}

	/**
	 * Adds the locations from the source file to the map
	 * 
	 * @param source
	 *            properties of the source to be added
	 */
	public void addSource(SourceProperties source) {
		for (TraceLocation location : source) {
			if (TraceBuilderConfiguration.GLOBAL_LOCATION_ASSERTS) {
				if (globalList.contains(location)) {
					TraceBuilderGlobals.getEvents().postAssertionFailed(
							"Location already in global list", //$NON-NLS-1$
							location.getConvertedName());
				} else {
					globalList.add(location);
				}
			}
			// Generates locationAdded event via TraceLocationListListener
			addNewLocationToTrace(location);
		}
		removeFromStorage(source);
	}

	/**
	 * Updates the locations based on the new source properties
	 * 
	 * @param source
	 *            the source properties of the updated source
	 */
	void updateSource(SourceProperties source) {
		for (TraceLocation location : source) {
			if (location.isDeleted()) {
				// Generates locationRemoved event via TraceLocationListListener
				locationDeleted(location);
			} else if (location.isContentChanged()) {
				locationContentChanged(location);
			} else {
				// Generates locationChanged events if offset or length has
				// changed
				location.notifyLocationChanged();
			}
		}
		checkGlobalValidity();
	}

	/**
	 * Removes the locations of the closed source properties
	 * 
	 * @param source
	 *            the source properties of the removed source
	 * @param path
	 *            the file path as string
	 * @param name
	 *            the file name
	 */
	public void removeSource(SourceProperties source, String path,
			String name) {
		missingSourceErrorShown = false;
		for (TraceLocation location : source) {
			if (TraceBuilderConfiguration.GLOBAL_LOCATION_ASSERTS) {
				if (!globalList.remove(location)) {
					TraceBuilderGlobals.getEvents().postAssertionFailed(
							"Location not in global list", //$NON-NLS-1$
							location.getConvertedName());
				}
			}
			TraceLocationList list = location.getLocationList();
			if (list != null) {
				addLastKnownLocation(source, location, list, path, name);
				// Generates locationRemoved event via TraceLocationListListener
				list.removeLocation(location);
			} else {
				if (TraceBuilderConfiguration.ASSERTIONS_ENABLED) {
					TraceBuilderGlobals.getEvents().postAssertionFailed(
							"Unassociated location on remove", //$NON-NLS-1$
							location.getConvertedName());
				}
			}
		}
	}	

	/**
	 * Converts a location to a last known  location
	 * 
	 * @param source
	 *            the source file
	 * @param location
	 *            the location
	 * @param list
	 *            the location list
	 * @param path
	 *            the file path as string
	 * @param name
	 *            the file name
	 */
	public void addLastKnownLocation(SourceProperties source,
			TraceLocation location, TraceLocationList list, String path, String name) {
		TraceObject owner = list.getOwner();
		if (owner instanceof Trace) {
			LastKnownLocationList pll = owner
					.getExtension(LastKnownLocationList.class);
			if (pll == null) {
				pll = new LastKnownLocationList();
				owner.addExtension(pll);
			}
			if (path == null) {
				path = source.getFilePath();
			}
			if (name == null) {
				name = source.getFileName();
			}			
			if (path != null && name != null) {
				if (new File(path + name).exists()) {
					String cname = null;
					String mname = null;
					SourceContext context = source.getSourceEditor()
							.getContext(location.getOffset());
					if (context != null) {
						cname = context.getClassName();
						mname = context.getFunctionName();
					}
					LastKnownLocation loc = new LastKnownLocation(path, name,
							location.getLineNumber(), cname, mname);
					pll.addLocation(loc);
				} else {
					showSourceRemovedError(name);
				}
			}
		}
	}	
	
	/**
	 * Shows source with traces removed error
	 * 
	 * @param name
	 *            the source name
	 */
	private void showSourceRemovedError(String name) {
		// TODO: Add an error code for this and move message there
		if (!missingSourceErrorShown) {
			missingSourceErrorShown = true;
			String msg = Messages
					.getString("TraceLocationMap.SourceWithTraceRemoved"); //$NON-NLS-1$
			TraceBuilderGlobals.getEvents().postErrorMessage(msg, name, true);
		}
	}

	/**
	 * Removes locations from last known storage
	 * 
	 * @param source
	 *            the source file
	 */
	private void removeFromStorage(SourceProperties source) {
		File f = new File(source.getFilePath() + source.getFileName());
		Iterator<TraceGroup> groups = model.getGroups();
		while (groups.hasNext()) {
			Iterator<Trace> traces = groups.next().getTraces();
			while (traces.hasNext()) {
				LastKnownLocationList list = traces.next().getExtension(
						LastKnownLocationList.class);
				if (list != null) {
					Iterator<LocationProperties> locs = list.iterator();
					while (locs.hasNext()) {
						LastKnownLocation loc = (LastKnownLocation) locs
								.next();
						File locf = new File(loc.getFilePath()
								+ loc.getFileName());
						if (f.equals(locf)) {
							locs.remove();
							list.fireLocationRemoved(loc);
						}
					}
				}
			}
		}
	}

	/**
	 * Adds a location to trace or to the unrelated list if a trace cannot be
	 * found.
	 * 
	 * @param location
	 *            the location to be added
	 */
	private void addNewLocationToTrace(TraceLocation location) {
		TraceLocationList list;
		Trace trace = model.findTraceByName(location.getOriginalName());
		if (trace != null) {
			list = trace.getExtension(TraceLocationList.class);
			if (list == null) {
				list = new TraceLocationList();
				trace.addExtension(list);
			}
		} else {
			String name = location.getParserRule().getLocationParser()
					.getLocationGroup();
			if (name == null) {
				list = unrelated;
			} else {
				list = parserGroups.get(name);
				if (list == null) {
					list = new TraceLocationList(name);
					model.addExtension(list);
					parserGroups.put(name, list);
				}
			}
		}
		list.addLocation(location);
	}

	/**
	 * Checks that all locations are valid.
	 * Posts assertion failed events if not valid
	 */
	private void checkGlobalValidity() {
		if (TraceBuilderConfiguration.GLOBAL_LOCATION_ASSERTS) {
			boolean failure = false;
			for (int i = 0; i < globalList.size(); i++) {
				TraceLocation loc = globalList.get(i);
				if (loc.isDeleted()) {
					TraceBuilderGlobals.getEvents().postAssertionFailed(
							"Deleted location found", //$NON-NLS-1$
							loc.getConvertedName());
					failure = true;
				} else if (loc.getLocationList() == null) {
					TraceBuilderGlobals.getEvents().postAssertionFailed(
							"Unassociated location found", //$NON-NLS-1$
							loc.getConvertedName());
					failure = true;
				} else if (loc.getTag() == null) {
					TraceBuilderGlobals.getEvents().postAssertionFailed(
							"Untagged location found", loc.getConvertedName()); //$NON-NLS-1$
					failure = true;
				} else if (loc.getLength() <= loc.getTag().length()) {
					TraceBuilderGlobals.getEvents().postAssertionFailed(
							"Unassociated location found", //$NON-NLS-1$
							loc.getConvertedName());
					failure = true;
				}
			}
			if (failure) {
				TraceBuilderGlobals.getEvents().postCriticalAssertionFailed(
						"Invalid location(s) found", null); //$NON-NLS-1$
			}
		}
	}

	/**
	 * Processes a location which has been deleted
	 * 
	 * @param location
	 *            the location that was deleted
	 */
	private void locationDeleted(TraceLocation location) {
		if (TraceBuilderConfiguration.GLOBAL_LOCATION_ASSERTS) {
			if (!globalList.remove(location)) {
				TraceBuilderGlobals.getEvents().postAssertionFailed(
						"Location not in global list", //$NON-NLS-1$
						location.getConvertedName());
			}
		}
		TraceLocationList list = location.getLocationList();
		if (list != null) {
			list.removeLocation(location);
		} else {
			if (TraceBuilderConfiguration.ASSERTIONS_ENABLED) {
				TraceBuilderGlobals.getEvents().postCriticalAssertionFailed(
						"Unassociated location on delete", //$NON-NLS-1$
						location.getConvertedName());
			}
		}
	}

	/**
	 * Processes a location that has content changed flag
	 * 
	 * @param location
	 *            the location to be processed
	 */
	private void locationContentChanged(TraceLocation location) {
		// If location is new, it does not have a list
		if (location.getLocationList() == null) {
			if (TraceBuilderConfiguration.GLOBAL_LOCATION_ASSERTS) {
				if (globalList.contains(location)) {
					TraceBuilderGlobals.getEvents().postAssertionFailed(
							"Location already in global list", //$NON-NLS-1$
							location.getConvertedName());
				} else {
					globalList.add(location);
				}
			}
			// Generates locationAdded event via TraceLocationListListener
			addNewLocationToTrace(location);
		} else if (location.isNameChanged()) {
			// Generates locationRemoved to listeners of old list and
			// locationAdded to listeners of new list
			moveLocation(location);
			// Generates content and validity changed events
			location.notifyLocationChanged();
		} else if (location.isContentChanged()) {
			// Generates content and validity changed events
			location.notifyLocationChanged();
		}
	}

	/**
	 * Moves a location from trace to another. Does nothing if the target trace
	 * is same as source trace
	 * 
	 * @param location
	 *            the location to be moved
	 */
	private void moveLocation(TraceLocation location) {
		Trace trace = location.getTrace();
		Trace newTrace = model.findTraceByName(location.getOriginalName());
		// If the traces differ, this relocates the location
		// to different location array
		if (trace != newTrace) {
			TraceLocationList list = location.getLocationList();
			// Removes from existing list and adds the existing list to the
			// updates list
			list.removeLocation(location);
			// Adds to new list. If new list does not exist, it is created
			if (newTrace != null) {
				list = newTrace.getExtension(TraceLocationList.class);
				if (list == null) {
					list = new TraceLocationList();
					newTrace.addExtension(list);
				}
			} else {
				list = unrelated;
			}
			list.addLocation(location);
		}
	}

	/**
	 * Returns the list of unrelated trace objects.
	 * 
	 * @return list of unrelated traces
	 */
	TraceLocationList getUnrelatedTraces() {
		return unrelated;
	}

	/**
	 * Removes all location lists from the model
	 */
	public void clearAll() {
		model.removeExtension(unrelated);
		Iterator<TraceGroup> groups = model.getGroups();
		while (groups.hasNext()) {
			TraceGroup group = groups.next();
			Iterator<Trace> traces = group.getTraces();
			while (traces.hasNext()) {
				Trace trace = traces.next();
				TraceLocationList list = trace
						.getExtension(TraceLocationList.class);
				trace.removeExtension(list);
			}
		}
	}

	/**
	 * Moves the locations from trace to unrelated list
	 * 
	 * @param trace
	 *            the trace
	 */
	void moveToUnrelated(Trace trace) {
		TraceLocationList list = trace.getExtension(TraceLocationList.class);
		if (list != null) {
			trace.removeExtension(list);
			for (LocationProperties loc : list) {
				unrelated.addLocation((TraceLocation) loc);
			}
		}
	}

	/**
	 * Moves locations from unrelated to the given trace
	 * 
	 * @param trace
	 *            the trace
	 */
	void moveFromUnrelated(Trace trace) {
		String name = trace.getName();
		TraceLocationList list = null;
		Iterator<LocationProperties> itr = unrelated.iterator();
		while (itr.hasNext()) {
			TraceLocation location = (TraceLocation) itr.next();
			if (name.equals(location.getOriginalName())) {
				list = trace.getExtension(TraceLocationList.class);
				if (list == null) {
					list = new TraceLocationList();
					trace.addExtension(list);
				}
				// NOTE: This must replicate the behavior of
				// TraceLocationList.removeLocation
				itr.remove();
				unrelated.fireLocationRemoved(location);
				list.addLocation(location);
			}
		}
	}
}