/******************************************************************************* * Copyright (c) 2008, 2010 Broadcom Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * James Blackburn (Broadcom Corp.) *******************************************************************************/ package org.eclipse.cdt.internal.core.settings.model.xml2; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import java.util.Queue; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.settings.model.ICStorageElement; import org.eclipse.cdt.internal.core.settings.model.CProjectDescriptionManager; import org.eclipse.cdt.internal.core.settings.model.CProjectDescriptionStorageManager; import org.eclipse.cdt.internal.core.settings.model.ICProjectDescriptionStorageType; import org.eclipse.cdt.internal.core.settings.model.ICProjectDescriptionStorageType.CProjectDescriptionStorageTypeProxy; import org.eclipse.cdt.internal.core.settings.model.xml.InternalXmlStorageElement; import org.eclipse.cdt.internal.core.settings.model.xml.XmlProjectDescriptionStorage; import org.eclipse.cdt.internal.core.settings.model.xml.XmlStorage; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceProxy; import org.eclipse.core.resources.IResourceProxyVisitor; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.osgi.framework.Version; import org.w3c.dom.Element; /** * This class extends XmlProjectDescriptionStroage to provide the following * functionality: * - Configuration Description storage modules are persisted in separate XML * files stored under .csettings in the project root directory. They * are linked into the main .cproject file via "externalCElementFile" element * * It is backwards compatible with XmlProjectDescriptionStorage. If it finds a file * with Version less than 5.0, it will delegate to the previous XmlProjectDescriptionStorage * * This allows users to more easily version control their CDT project descriptions * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=226457">Bug 226457</a> */ public class XmlProjectDescriptionStorage2 extends XmlProjectDescriptionStorage { /** Folder Name for storing Project specific configuration settings */ static final String STORAGE_FOLDER_NAME = ".csettings"; //$NON-NLS-1$ /** Element providing the name of the external C Element file */ public static final String EXTERNAL_CELEMENT_KEY = "externalCElementFile"; //$NON-NLS-1$ private static final String ID = "id"; //$NON-NLS-1$ /** Override the description version, we support version '5.0' files */ @SuppressWarnings("hiding") public static final Version STORAGE_DESCRIPTION_VERSION = new Version("5.0"); //$NON-NLS-1$ /** The storage type id of this extended project description type */ @SuppressWarnings("hiding") public static final String STORAGE_TYPE_ID = CCorePlugin.PLUGIN_ID + ".XmlProjectDescriptionStorage2"; //$NON-NLS-1$ /** Map from storageModuleId to modification time stamp */ Map<String, Long> modificationMap = Collections.synchronizedMap(new HashMap<String, Long>()); public XmlProjectDescriptionStorage2(CProjectDescriptionStorageTypeProxy type, IProject project, Version version) { super(type, project, version); } /* * Check for external modification in the module files in the .csettings directory * (non-Javadoc) * @see org.eclipse.cdt.internal.core.settings.model.xml.XmlProjectDescriptionStorage#checkExternalModification() */ @Override protected synchronized boolean checkExternalModification() { // Check if our parent thinks we need a reload if (super.checkExternalModification()) return true; // If not currently loaded, then nothing to do if (getLoadedDescription() == null) return false; final boolean[] needReload = new boolean[] { false }; try { project.getFolder(STORAGE_FOLDER_NAME).accept(new IResourceProxyVisitor() { public boolean visit(IResourceProxy proxy) throws CoreException { if (modificationMap.containsKey(proxy.getName())) { long modStamp = getModificationStamp(proxy.requestResource()); if (modificationMap.get(proxy.getName()) != modStamp) { // There may be old storages in here, ensure we don't infinite reload... modificationMap.put(proxy.getName(), modStamp); setCurrentDescription(null, true); needReload[0] = true; } } return true; } }, IResource.NONE); } catch (CoreException e) { // STORAGE_FOLDER_NAME doesn't exist... or something went wrong during reload if (project.getFolder(STORAGE_FOLDER_NAME).exists()) CCorePlugin.log("XmlProjectDescriptionStorage2: Problem checking for external modification: " + e.getMessage()); //$NON-NLS-1$ } return needReload[0]; } /* * (non-Javadoc) * @see org.eclipse.cdt.internal.core.settings.model.xml.XmlProjectDescriptionStorage#createStorage(org.eclipse.core.resources.IContainer, java.lang.String, boolean, boolean, boolean) */ @Override protected final InternalXmlStorageElement createStorage(IContainer container, String fileName, boolean reCreate, boolean createEmptyIfNotFound, boolean readOnly) throws CoreException { InternalXmlStorageElement el = super.createStorage(container, fileName, reCreate, createEmptyIfNotFound, readOnly); Queue<ICStorageElement> nodesToCheck = new LinkedList<ICStorageElement>(); nodesToCheck.addAll(Arrays.asList(el.getChildren())); while (!nodesToCheck.isEmpty()) { ICStorageElement currEl = nodesToCheck.remove(); // If not a storageModule element or doesn't have EXTERNAL_CELEMENT_KEY, continue if (!XmlStorage.MODULE_ELEMENT_NAME.equals(currEl.getName()) || currEl.getAttribute(EXTERNAL_CELEMENT_KEY) == null) { nodesToCheck.addAll(Arrays.asList(currEl.getChildren())); continue; } try { // Found -- load the Document pointed to (allows multiple layers of indirection if need be) InternalXmlStorageElement el2 = createStorage(project.getFolder(STORAGE_FOLDER_NAME), currEl.getAttribute(EXTERNAL_CELEMENT_KEY), reCreate, createEmptyIfNotFound, readOnly); // Update the modification stamp modificationMap.put(currEl.getAttribute(EXTERNAL_CELEMENT_KEY), getModificationStamp(project.getFolder(STORAGE_FOLDER_NAME).getFile(currEl.getAttribute(EXTERNAL_CELEMENT_KEY)))); ICStorageElement currParent = currEl.getParent(); // Get the storageModule element in the new Document ICStorageElement[] childStorages = el2.getChildrenByName(XmlStorage.MODULE_ELEMENT_NAME); Assert.isTrue(childStorages.length == 1, "More than one storageModule in external Document!"); //$NON-NLS-1$ // Import the loaded element currParent.importChild(childStorages[0]); // Remove the placeholder currParent.removeChild(currEl); } catch (CoreException e) { // If we're here there was a problem loading csettings when there is a .cproject file. CCorePlugin.log(e); throw e; } } return el; } /* * Serialize the Project Description: * - The .cproject file contains the root XML Elements. * - We serialize the storageModule children of the CConfiguration elements (in the org.eclipse.cdt.settings module) * to separate files in the .csettings directory to prevent unmanageably large XML deltas * Return the modification stamp of the main .cproject file as our super method does * (non-Javadoc) * @see org.eclipse.cdt.internal.core.settings.model.xml.XmlProjectDescriptionStorage#serialize(org.eclipse.core.resources.IContainer, java.lang.String, org.eclipse.cdt.core.settings.model.ICStorageElement) */ @Override protected long serialize(IContainer container, String file, ICStorageElement element) throws CoreException { // If the current loaded version is less than our version, then delegate to parent to handle persisting // i.e. don't auto-upgrade if (version.compareTo(type.version) < 0) return super.serialize(container, file, element); // Copy the original passed in element, as we're going to re-write the children InternalXmlStorageElement copy = copyElement(element, false); // Map containing external CConfiguration elements to be serialized Map<String, InternalXmlStorageElement> externalStorageElements = new HashMap<String, InternalXmlStorageElement>(); // Iterate through the initial children for (ICStorageElement el : copy.getChildren()) { // Only interested in root level org.eclipse.cdt.settings storageModules if (!CProjectDescriptionManager.MODULE_ID.equals(el.getAttribute(XmlStorage.MODULE_ID_ATTRIBUTE))) continue; Queue<ICStorageElement> configStorages = new LinkedList<ICStorageElement>(); configStorages.addAll(Arrays.asList(el.getChildren())); while (!configStorages.isEmpty()) { InternalXmlStorageElement iEl = (InternalXmlStorageElement)configStorages.remove(); // If not a stroageModule element, the just add children ... // e.g. configuration element... if (iEl.getAttribute(XmlStorage.MODULE_ID_ATTRIBUTE) == null) { configStorages.addAll(Arrays.asList(iEl.getChildren())); continue; } // Persist this storage Element fileName = configurationID_moduleID String storageId = ((Element)iEl.fElement.getParentNode()).getAttribute(ID) + "_" //$NON-NLS-1$ + iEl.getAttribute(XmlStorage.MODULE_ID_ATTRIBUTE); // create a dummy storage for the element <cproject> held InternalXmlStorageElement newEl = createStorage(project.getFolder(STORAGE_FOLDER_NAME), storageId, false, true, false); newEl.importChild(copyElement(iEl, false)); externalStorageElements.put(storageId, newEl); // Clear other attributes and children iEl.clear(); // Set the external c element key iEl.fElement.setAttribute(EXTERNAL_CELEMENT_KEY, storageId); } } // Serialize the Root XML document Element (.cproject) long cproject_mod_time = super.serialize(project, ICProjectDescriptionStorageType.STORAGE_FILE_NAME, copy); // Check that the .csettings directory exists and is writable IFolder csettings = project.getFolder(STORAGE_FOLDER_NAME); try { CProjectDescriptionStorageManager.ensureWritable(csettings); if (!csettings.exists()) csettings.create(true, true, new NullProgressMonitor()); } catch (CoreException e) { if (!csettings.exists()) throw e; } // Serialize all the external elements for (Map.Entry<String, InternalXmlStorageElement> e : externalStorageElements.entrySet()) modificationMap.put(e.getKey(), super.serialize(csettings, e.getKey(), e.getValue())); return cproject_mod_time; } @Override protected Version getVersion() { return STORAGE_DESCRIPTION_VERSION; } @Override protected String getStorageTypeId() { return STORAGE_TYPE_ID; } }