project/com.nokia.carbide.cpp.epoc.engine/src/com/nokia/carbide/internal/cpp/epoc/engine/model/ViewBase.java
changeset 0 fb279309251b
child 610 bfb3ab3f70f2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/project/com.nokia.carbide.cpp.epoc.engine/src/com/nokia/carbide/internal/cpp/epoc/engine/model/ViewBase.java	Fri Apr 03 23:33:03 2009 +0100
@@ -0,0 +1,563 @@
+/*
+* Copyright (c) 2006-2009 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of the License "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: 
+*
+*/
+
+package com.nokia.carbide.internal.cpp.epoc.engine.model;
+
+import com.nokia.carbide.cpp.epoc.engine.DocumentFactory;
+import com.nokia.carbide.cpp.epoc.engine.EpocEnginePlugin;
+import com.nokia.carbide.cpp.epoc.engine.model.IModel;
+import com.nokia.carbide.cpp.epoc.engine.model.IOwnedModel;
+import com.nokia.carbide.cpp.epoc.engine.model.IView;
+import com.nokia.carbide.cpp.epoc.engine.model.IViewConfiguration;
+import com.nokia.carbide.cpp.epoc.engine.model.IViewListener;
+import com.nokia.carbide.internal.cpp.epoc.engine.parser.ITranslationUnitParser;
+import com.nokia.cpp.internal.api.utils.core.*;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jface.text.IDocument;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base implementation of a view
+ */
+public abstract class ViewBase<Model extends IOwnedModel> implements IView<Model> {
+	protected ModelBase model;
+
+	protected IMessage[] messages;
+
+	protected IViewConfiguration viewConfiguration;
+
+	protected List<IViewListener> listeners;
+
+	protected boolean outOfSync;
+
+	protected ITranslationUnitParser parser;
+
+	protected IPath[] referencedPaths;
+
+	// last value passed to #updateCurrentDirectory()
+	protected IPath lastDirectoryPath;
+
+	protected IPath currentDirectory;
+
+	protected IPath projectPath;
+
+	protected boolean debug;
+
+	private int slashBackCount;
+
+	private int slashFwdCount;
+
+
+	public ViewBase(ModelBase model, ITranslationUnitParser parser,
+			IViewConfiguration viewConfiguration) {
+		Check.checkArg(model);
+
+		this.parser = parser;
+		this.model = model;
+		this.viewConfiguration = viewConfiguration;
+		this.listeners = new LinkedList<IViewListener>();
+	}
+
+	/**
+	 * Reparse the preprocessor TU.
+	 * <p>
+	 * Clients must:
+	 * <p>
+	 * <li>set the TU
+	 * <li>set the ppTu
+	 * @param documentMap if non-null, a set of existing documents that overrides any that would
+	 * otherwise be used while parsing (may be incomplete) 
+	 * @return the (new) map of full path to document detected on parse
+	 */
+	abstract protected Map<IPath, IDocument> internalReparse(Map<IPath, IDocument> documentMap);
+
+	/**
+	 * Reparse the preprocessor TU using translation units and documents from the translation unit provider.
+	 */
+	protected void reparse(boolean useLoadedDocuments) {
+		synchronized (model) {
+			Map<IPath, IDocument> documentMap;
+			
+			slashFwdCount = slashBackCount = 0;
+
+			if (useLoadedDocuments) {
+				Map<IPath, IDocument> existingDocumentMap = model.getDocumentMap();
+				documentMap = internalReparse(existingDocumentMap);
+			} else {
+				documentMap = internalReparse(null);
+			}
+
+			model.mergeDocumentMap(documentMap);
+		}
+
+		this.messages = null;
+		this.referencedPaths = null;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#dispose()
+	 */
+	public void dispose() {
+		listeners.clear();
+		model.viewDisposed(this);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#getModel()
+	 */
+	public Model getModel() {
+		return (Model) model;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#getViewConfiguration()
+	 */
+	public IViewConfiguration getViewConfiguration() {
+		return viewConfiguration;
+	}
+
+	/* (non-Javadoc)
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#getMessages()
+	 */
+	public IMessage[] getMessages() {
+		if (messages == null) {
+			final List<IMessage> messageList = new ArrayList<IMessage>();
+			addViewSpecificMessages(messageList);
+			messages = (IMessage[]) messageList.toArray(new IMessage[messageList.size()]);
+		}		
+		return messages;
+	}
+
+	/**
+	 * Add messages to the list specific to issues encountered while parsing the filtered TU.
+	 * @param messageList
+	 */
+	abstract protected void addViewSpecificMessages(List<IMessage> messageList);
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#getReferencedFiles()
+	 */
+	abstract public IPath[] getReferencedFiles();
+
+	/**
+	 * Tell whether the view contains uncommitted changes.
+	 * 
+	 * @return true: changes present
+	 */
+	protected abstract boolean internalHasChanges();
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#hasChanges()
+	 */
+	public synchronized boolean hasChanges() {
+		return internalHasChanges();
+	}
+
+	/**
+	 * Revert the changes stored in the view. This is called from
+	 * {@link #revert()} and
+	 * {@link IModel#createView(IViewConfiguration) }. The DOM will be
+	 * reparsed automatically.
+	 */
+	protected abstract void internalRevertChanges();
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#revert()
+	 */
+	public synchronized void revert() {
+		doRevert(false);
+	}
+	
+	protected void doRevert(boolean useLoadedDocuments) {
+		synchronized (model) {
+			if (viewConfiguration != null)
+				projectPath = viewConfiguration.getViewParserConfiguration().getProjectLocation();
+
+			reparse(useLoadedDocuments);
+
+			lastDirectoryPath = null;
+			updateCurrentDirectory(getModel().getPath());
+			
+			internalRevertChanges();
+			
+			outOfSync = false;
+	
+			fireChanged();
+		}
+	}
+
+	/**
+	 * Implement to handle the commit operation, which calls #internalApplyChanges().
+	 * Called after changes have been detected, with the model synchronized.
+	 */
+	protected abstract void internalCommit();
+	
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#commit()
+	 */
+	public synchronized void commit() throws IllegalStateException {
+		Map<IPath, IDocument> savedDocumentMap = null;
+		
+		synchronized (model) {
+			if (outOfSync) {
+				// read javadoc for details
+				throw new IllegalStateException("view is out of sync with model"); //$NON-NLS-1$
+			}
+	
+			// shortcut
+			if (!hasChanges())
+				return;
+
+			internalCommit();
+
+			// save off the committed documents...
+			if (model.getModelProvider() != null) {
+				savedDocumentMap = copyDocumentMap(model.getDocumentMap());
+			}
+		}
+		
+		// outside the synchronization, now save the documents
+		// (this prevents deadlock when a model's documents are
+		// being saved and another thread tries to commit)
+		if (model.getModelProvider() != null) {
+			try {
+				model.getModelProvider().save(model, savedDocumentMap);
+			} catch (CoreException e) {
+				EpocEnginePlugin.log(e, "Error saving documents");
+			}
+		}
+	}
+
+	/**
+	 * Copy the document map
+	 * @param documentMap
+	 * @return
+	 */
+	private Map<IPath, IDocument> copyDocumentMap(Map<IPath, IDocument> documentMap) {
+		Map<IPath, IDocument> copy = new HashMap<IPath, IDocument>();
+		for (Map.Entry<IPath, IDocument> entry : documentMap.entrySet()) {
+			copy.put(entry.getKey(), DocumentFactory.createDocument(entry.getValue().get()));
+		}
+		return copy;
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#markUnsynchronized()
+	 */
+	public void markUnsynchronized() {
+		synchronized (model) {
+			outOfSync = true;
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#needsSynchonize()
+	 */
+	public synchronized boolean needsSynchonize() {
+		synchronized (model) {
+			return outOfSync;
+		}
+	}
+
+	public abstract boolean merge() ;
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#forceSynchronized()
+	 */
+	public synchronized void forceSynchronized() {
+		synchronized (model) {
+			outOfSync = false;
+		}
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#addListener(com.nokia.carbide.cpp.epoc.engine.model.IViewListener)
+	 */
+	public synchronized void addListener(IViewListener listener) {
+		Check.checkArg(listener);
+		if (!listeners.contains(listener))
+			listeners.add(listener);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see com.nokia.carbide.cpp.epoc.engine.model.IView#removeListener(com.nokia.carbide.cpp.epoc.engine.model.IViewListener)
+	 */
+	public synchronized void removeListener(IViewListener listener) {
+		listeners.remove(listener);
+	}
+
+	protected void fireChanged() {
+		for (IViewListener listener : listeners) {
+			listener.viewChanged(this);
+		}
+	}
+
+	/**
+	 * Convert an IPath to a relative path
+	 * 
+	 * @param basePath
+	 *            path against to get relative path
+	 * @param projectPath
+	 *            project-relative
+	 * @return relative directory (or projectPath if not relative)
+	 */
+	static public IPath fromProjectToRelativePath(IPath basePath, IPath projectPath) {
+		return fromProjectToRelativePath(basePath, projectPath, true);
+	}
+
+	/**
+	 * Convert an IPath to a relative path
+	 * 
+	 * @param basePath
+	 *            path against to get relative path
+	 * @param projectPath
+	 *            project-relative
+	 * @param allowParent
+	 *            allow returning relative to the parent(s) of the basePath? 
+	 * @return relative directory (or projectPath if not relative)
+	 */
+	static public IPath fromProjectToRelativePath(IPath basePath, IPath projectPath, boolean allowParent) {
+		if (basePath == null)
+			return projectPath;
+		if (basePath.getDevice() != null && projectPath.getDevice() != null) {
+			if (!basePath.getDevice().equalsIgnoreCase(projectPath.getDevice()))
+				return projectPath;
+		} else if (basePath.getDevice() != null || projectPath.getDevice() != null) {
+			return projectPath;
+		}
+		int match = FileUtils.matchingFirstSegments(basePath, projectPath);
+		IPath partial = projectPath.removeFirstSegments(match).setDevice(null);
+		if (match < basePath.segmentCount() && !basePath.toString().equals(".")) { //$NON-NLS-1$
+			if (!allowParent)
+				return projectPath;
+			
+			// prepend a ".." to each basePath element to get down to the
+			// project
+			for (int i = match; i < basePath.segmentCount(); i++)
+				partial = new Path("..").append(partial); //$NON-NLS-1$
+		} else if (partial.segmentCount() == 0) {
+			// don't do this, since paths with '.' are mangled by Path()
+			//partial = new Path("."); //$NON-NLS-1$
+		}
+		return partial.makeRelative();
+	}
+
+	/**
+	 * Combine a path and a filename.
+	 * 
+	 * @param path
+	 * @param name
+	 * @return
+	 */
+	static public IPath combinePaths(IPath path, String name) {
+		if (path == null)
+			return FileUtils.createPossiblyRelativePath(name);
+		else
+			return path.append(FileUtils.createPossiblyRelativePath(name));
+	}
+
+	static public boolean equalPath(IPath path, IPath path2) {
+		return path.toOSString().equalsIgnoreCase(path2.toOSString());
+	}
+
+	/**
+	 * Get the project location-relative directory for the given source file.
+	 * @param fullPath
+	 */
+	protected IPath getSourceRelativePath(IPath fullPath, boolean allowParent) {
+		return fromProjectToRelativePath(projectPath, fullPath.removeLastSegments(1), allowParent);
+	}
+
+	/**
+	 * Get project path.
+	 * @return IPath
+	 */
+	public IPath getProjectPath() {
+		return projectPath;
+	}
+	
+	/**
+	 * Update the current directory to the given path.
+	 * @param path
+	 */
+	public void updateCurrentDirectory(IPath fullPath) {
+		if (lastDirectoryPath == null || !lastDirectoryPath.equals(fullPath)) {
+			currentDirectory = getSourceRelativePath(fullPath, false);
+			lastDirectoryPath = fullPath;
+		}
+		
+	}
+	
+	/**
+	 * Get the current directory while scanning or updating.
+	 * @return
+	 */
+	public IPath getCurrentDirectory() {
+		return currentDirectory;
+	}
+	
+	public void setDebug(boolean debug) {
+		this.debug = debug;
+	}
+	
+	public IPath convertModelToProjectPath(IPath modelPath) {
+		if (modelPath == null || modelPath.isAbsolute())
+			return modelPath;
+		return fromProjectToRelativePath(getProjectPath(), model.getPath().removeLastSegments(1).append(modelPath), true);
+	}
+	
+	public IPath convertProjectToModelPath(IPath prjPath) {
+		if (prjPath == null || prjPath.isAbsolute())
+			return prjPath;
+		return fromProjectToRelativePath(model.getPath().removeLastSegments(1), getProjectPath().append(prjPath));
+	}
+	
+	/**
+	 * Symbian coding styles prefer forward slashes, but we want to adhere
+	 * to the existing style as much as possible.  This routine tracks
+	 * that information per source line.  We assume that all slashes are
+	 * either comments, catenations, or parts of pathnames (it's done this
+	 * way rather than peppering the entirely of the parser with calls to this).
+	 * @param text the original text of something (node, statement, file, etc.)
+	 */
+	protected void recordSlashInfo(String text) {
+		if (text == null)
+			return;
+		int idx, len = text.length();
+		for (idx = 0; idx < len; idx++) {
+			char ch = text.charAt(idx);
+			char nextCh = (idx + 1 < len) ? text.charAt(idx + 1) : 0;
+			if (ch == '\\') {
+				// ignore backslash used in catenated lines
+				if (nextCh == '\r' || nextCh == '\n') {
+					idx++;
+					continue;
+				}
+				slashBackCount++;
+			} else if (ch == '/') {
+				// ignore comments
+				if (nextCh == '*' || nextCh == '/') {
+					idx++;
+					continue;
+				}
+				slashFwdCount++;
+			} else if (ch == '*' && nextCh == '/') {
+				// ignore comments
+				idx++;
+			}
+		}
+		
+	}
+
+	/** 
+	 * This enum defines what kind of slash format we want for paths 
+	 * emitted into text.
+	 */
+	protected enum SlashFormat {
+		/** Always use forward slashes. */
+		FORWARD,
+		/** Always use backslashes. */
+		BACKWARD,
+		/** Use the predominant style of slash. */
+		PREDOMINANT
+	};
+	
+	/** 
+	 * Return the slash format to use. 
+	 * Subclasses may override.
+	 * @return SlashFormat
+	 */
+	protected SlashFormat getSlashFormat() {
+		return SlashFormat.PREDOMINANT;
+	}
+	
+	/**
+	 * Get a path converted to the predominant or expected style.
+	 * The handling of the predominant style relies on the view having used 
+	 * {@link #recordSlashInfo(String)} when reparsing.
+	 * @param IPath
+	 * @return String in predominant format
+	 */
+	public String pathString(IPath path) {
+		SlashFormat format = getChosenSlashFormat();
+		String string = path.toString();
+		if (format == SlashFormat.FORWARD)
+			return string;
+		else
+			return string.replace("/", "\\");
+	}
+
+	/**
+	 * Get the slash format we're deciding to use, based either on the 
+	 * fixed choice from {@link #getSlashFormat()} or the actual predominant format
+	 * detected in source.
+	 * @return SlashFormat, never {@link SlashFormat#PREDOMINANT}
+	 */
+	private SlashFormat getChosenSlashFormat() {
+		SlashFormat format = getSlashFormat();
+		if (format == SlashFormat.PREDOMINANT) {
+			format = (slashFwdCount >= slashBackCount) ? SlashFormat.FORWARD : SlashFormat.BACKWARD;
+		}
+		return format;
+	}
+	
+	/**
+	 * Get the path separator for the predominant or expected style.
+	 * The handling of the predominant style relies on the view having used 
+	 * {@link #recordSlashInfo(String)} when reparsing.
+	 * @return '/' or '\\'
+	 */
+	public char pathSeparator() {
+		SlashFormat format = getChosenSlashFormat();
+		if (format == SlashFormat.FORWARD)
+			return '/';
+		else
+			return '\\';
+	}
+}