/******************************************************************************* * Copyright (c) 2008, 2011 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; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.settings.model.ICProjectDescription; import org.eclipse.cdt.internal.core.CConfigBasedDescriptorManager; import org.eclipse.cdt.internal.core.XmlUtil; import org.eclipse.cdt.internal.core.settings.model.ICProjectDescriptionStorageType.CProjectDescriptionStorageTypeProxy; import org.eclipse.cdt.internal.core.settings.model.xml.XmlProjectDescriptionStorage; import org.eclipse.cdt.internal.core.settings.model.xml2.XmlProjectDescriptionStorage2; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileInfo; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourceAttributes; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Platform; import org.osgi.framework.Version; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.ProcessingInstruction; /** * Class that marshals creation of AbstractCProjectDescriptionStorages * for a given project. * * Persist Storage type ID in the .cproject file, and provides backwards compatibility * for existing project descriptions which don't encode the storage type in the project * description. */ public class CProjectDescriptionStorageManager { /* Extension point data */ /** CProjectDescriptionStorage extension point ID */ private static final String CPROJ_DESC_STORAGE_EXT_ID = "CProjectDescriptionStorage"; //$NON-NLS-1$ /** storage type element ID */ private static final String CPROJ_STORAGE_TYPE = "CProjectStorageType"; //$NON-NLS-1$ // TODO provide some UI to select this /** Default project description Storage type */ private static final String DEFAULT_STORAGE_TYPE = XmlProjectDescriptionStorage.STORAGE_TYPE_ID; // XmlProjectDescriptionStorage2.STORAGE_TYPE_ID; /** Default project description Storage version */ private static final Version DEFAULT_STORAGE_VERSION = XmlProjectDescriptionStorage.STORAGE_DESCRIPTION_VERSION; // XmlProjectDescriptionStorage2.STORAGE_DESCRIPTION_VERSION; /** Map of StorageType ID -> List of StorageTypes */ private volatile Map<String, List<CProjectDescriptionStorageTypeProxy>> storageTypeMap; /** Map from IProject -> AbstractCProjectDescriptionStorage which is responsible for (de)serializing the project */ private ConcurrentHashMap<IProject, AbstractCProjectDescriptionStorage> fDescriptionStorageMap = new ConcurrentHashMap<IProject, AbstractCProjectDescriptionStorage>(); private volatile static CProjectDescriptionStorageManager instance; private CProjectDescriptionStorageManager() {} public static CProjectDescriptionStorageManager getInstance() { if (instance == null) { synchronized (CProjectDescriptionStorageManager.class) { if (instance == null) instance = new CProjectDescriptionStorageManager(); } } return instance; } /** * Return a AbstractCProjectDescriptionStorage for a particular project or * null if none were found and the default storage type wasn't found * @param project * @return project description storage or null */ public AbstractCProjectDescriptionStorage getProjectDescriptionStorage(IProject project) { if (!project.isAccessible()) { assert(!fDescriptionStorageMap.contains(project)); return null; } AbstractCProjectDescriptionStorage projStorage = fDescriptionStorageMap.get(project); if (projStorage == null) { projStorage = loadProjectStorage(project); fDescriptionStorageMap.putIfAbsent(project, projStorage); } return fDescriptionStorageMap.get(project); } /** * Sets the Project description by delegating to the appropriate project storage type. * * If the storage type returns false on {@link ICProjectDescriptionStorageType#createsCProjectXMLFile()} then * we create a .cproject file with type id and version. * * If no existing project storage is found then we throw a core exception (users should have called * #getProjectDescriptionStorage(...) before calling this. * * @param project * @param description * @throws CoreException on failure */ public void setProjectDescription(IProject project, ICProjectDescription description, int flags, IProgressMonitor monitor) throws CoreException { AbstractCProjectDescriptionStorage storage = fDescriptionStorageMap.get(project); if (storage == null) throw ExceptionFactory.createCoreException("Can't set ProjectDescription before getProjectDescriptionStorage!"); //$NON-NLS-1$ if (!storage.type.createsCProjectXMLFile()) writeProjectStorageType(project, storage.type); storage.setProjectDescription(description, flags, monitor); } /** * Persist the type and version of this particular project description storage type * (for description storages that don't perform this job themselves). * @param project * @param type */ private void writeProjectStorageType(IProject project, CProjectDescriptionStorageTypeProxy type) throws CoreException{ Document doc; try { doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); // Set the version ProcessingInstruction instruction = doc.createProcessingInstruction(ICProjectDescriptionStorageType.STORAGE_VERSION_NAME, type.version.toString()); doc.appendChild(instruction); // Set the type id Element el = doc.createElement(ICProjectDescriptionStorageType.STORAGE_ROOT_ELEMENT_NAME); el.setAttribute(ICProjectDescriptionStorageType.STORAGE_TYPE_ATTRIBUTE, type.id); doc.appendChild(el); XmlUtil.prettyFormat(doc); ByteArrayOutputStream stream = new ByteArrayOutputStream(); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$ transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$ transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ DOMSource source = new DOMSource(doc); StreamResult result = new StreamResult(stream); transformer.transform(source, result); InputStream input = new ByteArrayInputStream(stream.toByteArray()); // Set the project description storage type IFile f = project.getFile(ICProjectDescriptionStorageType.STORAGE_FILE_NAME); if (!f.exists()) f.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); ensureWritable(f); if (!f.exists()) f.create(input, true, new NullProgressMonitor()); else f.setContents(input, IResource.FORCE, new NullProgressMonitor()); } catch (ParserConfigurationException e) { throw ExceptionFactory.createCoreException(e); } catch (TransformerConfigurationException e) { throw ExceptionFactory.createCoreException(e); } catch (TransformerFactoryConfigurationError e) { throw ExceptionFactory.createCoreException(e); } catch (TransformerException e) { throw ExceptionFactory.createCoreException(e); } } /** * Given a project, this method attempts to discover the type of the storage * and return the AbstractCProjectDescriptionStorage responsible for loading it. * * @return AbstractCProjectDescription or null if not found */ private AbstractCProjectDescriptionStorage loadProjectStorage(IProject project) { if (storageTypeMap == null) initExtensionPoints(); // If no project description found, then use the default Version version = DEFAULT_STORAGE_VERSION; String storageTypeID = DEFAULT_STORAGE_TYPE; InputStream stream = null; try{ DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); stream = getInputStreamForIFile(project, ICProjectDescriptionStorageType.STORAGE_FILE_NAME); if(stream != null){ Document doc = builder.parse(stream); // Get the first element in the project file Node rootElement = doc.getFirstChild(); if (rootElement.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE) throw ExceptionFactory.createCoreException(SettingsModelMessages.getString("CProjectDescriptionManager.7")); //$NON-NLS-1$ else version = new Version(rootElement.getNodeValue()); // Now get the project root element (there should be only one) NodeList nodes = doc.getElementsByTagName(ICProjectDescriptionStorageType.STORAGE_ROOT_ELEMENT_NAME); if (nodes.getLength() == 0) throw ExceptionFactory.createCoreException(SettingsModelMessages.getString("CProjectDescriptionManager.9")); //$NON-NLS-1$ Node node = nodes.item(0); if(node.getNodeType() != Node.ELEMENT_NODE) throw ExceptionFactory.createCoreException(SettingsModelMessages.getString("CProjectDescriptionManager.10")); //$NON-NLS-1$ // If we've got this far, then we're at least dealing with an old style project: // as this didn't use to provide a type, specify one explicitly // Choose new style -- separated out storage modules by default as this is backwards compatible... storageTypeID = XmlProjectDescriptionStorage2.STORAGE_TYPE_ID; if (((Element)node).hasAttribute(ICProjectDescriptionStorageType.STORAGE_TYPE_ATTRIBUTE)) storageTypeID = ((Element)node).getAttribute(ICProjectDescriptionStorageType.STORAGE_TYPE_ATTRIBUTE); } } catch (Exception e) { // Catch all, if not found, we use the old-style defaults... } finally { if(stream != null){ try { stream.close(); } catch (IOException e) {} } } List<CProjectDescriptionStorageTypeProxy> types = storageTypeMap.get(storageTypeID); if (types != null) { for (CProjectDescriptionStorageTypeProxy type : types) { if (type.isCompatible(version)) { return type.getProjectDescriptionStorage(type, project, version); } } } // No type found! CCorePlugin.log("CProjectDescriptionStorageType: " + storageTypeID + " for version: " + version + " not found!"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ return null; } private InputStream getInputStreamForIFile(IProject project, String name) throws CoreException { IFile f = project.getFile(name); if (f.exists()) return f.getContents(true); else { URI location = f.getLocationURI(); if (location != null) { IFileStore file = EFS.getStore(location); IFileInfo info = null; if (file != null) { info = file.fetchInfo(); if (info != null && info.exists()) return file.openInputStream(EFS.NONE, null); } } } throw ExceptionFactory.createCoreException("No project des file found..."); //$NON-NLS-1$ } /* * Resource Change Callbacks */ /** * Callback indicating that a project has moved * @param fromProject * @param toProject */ void projectMove(IProject fromProject, IProject toProject) { AbstractCProjectDescriptionStorage projStorage = fDescriptionStorageMap.get(fromProject); if (projStorage != null) { fDescriptionStorageMap.put(toProject, projStorage); projStorage.projectMove(toProject); fDescriptionStorageMap.remove(fromProject); } // Notify the CConfigBasedDescriptorManager as well CConfigBasedDescriptorManager.getInstance().projectMove(fromProject, toProject); } /** * Callback indicating project has been closed or deleted * @param project */ void projectClosedRemove(IProject project) { AbstractCProjectDescriptionStorage projStorage = fDescriptionStorageMap.get(project); if (projStorage != null) projStorage.projectCloseRemove(); fDescriptionStorageMap.remove(project); // Remove from ICDescriptorManager as well CConfigBasedDescriptorManager.getInstance().projectClosedRemove(project); } /** * Cleanup state */ public void shutdown() { instance = null; } /** * Initialize the project description storage types */ private synchronized void initExtensionPoints() { if (storageTypeMap != null) return; Map<String, List<CProjectDescriptionStorageTypeProxy>> m = new HashMap<String, List<CProjectDescriptionStorageTypeProxy>>(); IExtensionPoint extpoint = Platform.getExtensionRegistry().getExtensionPoint(CCorePlugin.PLUGIN_ID, CPROJ_DESC_STORAGE_EXT_ID); for (IExtension extension : extpoint.getExtensions()) { for (IConfigurationElement configEl : extension.getConfigurationElements()) { if (configEl.getName().equalsIgnoreCase(CPROJ_STORAGE_TYPE)) { CProjectDescriptionStorageTypeProxy type = initStorageType(configEl); if (type != null) { if (!m.containsKey(type.id)) m.put(type.id, new LinkedList<CProjectDescriptionStorageTypeProxy>()); m.get(type.id).add(type); } } } } storageTypeMap = m; } /** * Initialize a storage type from a configuration element * @param el * @return */ private static CProjectDescriptionStorageTypeProxy initStorageType(IConfigurationElement el) { CProjectDescriptionStorageTypeProxy type = null; try { type = new CProjectDescriptionStorageTypeProxy(el); } catch (CoreException e) { CCorePlugin.log("Couldn't instantiate CProjectDescriptionStorageType " + //$NON-NLS-1$ el.getDeclaringExtension().getNamespaceIdentifier() + " " + e.getMessage()); //$NON-NLS-1$ } catch (IllegalArgumentException e) { CCorePlugin.log("Failed to load CProjectDescriptionStorageType " + //$NON-NLS-1$ el.getDeclaringExtension().getNamespaceIdentifier() + " " + e.getMessage()); //$NON-NLS-1$ } return type; } /** * Helper method to ensure that a resource is writable. This means: <br/> * - If the resource doesn't exist, it can be created (its parent is made writable) <br/> * - If the resource exists and its a file, it's made writable <br/> * - If the resource exists and its a directory, it and its (direct) children * are made writable * @param resource * @throws CoreException on failure */ public static void ensureWritable(IResource resource) throws CoreException { if (!resource.exists()) resource.refreshLocal(IResource.DEPTH_INFINITE, null); if (!resource.exists()) { // If resource doesn't exist, ensure it can be created. ResourceAttributes parentAttr = resource.getParent().getResourceAttributes(); if (parentAttr.isReadOnly()) { parentAttr.setReadOnly(false); resource.getParent().setResourceAttributes(parentAttr); } } else { // If resource exists, ensure it and children are writable if (resource instanceof IFile) { if (resource.getResourceAttributes().isReadOnly()) { IStatus result = resource.getWorkspace().validateEdit(new IFile[] { (IFile) resource }, null); if (!result.isOK()) throw new CoreException(result); } } else if (resource instanceof IContainer) { ResourceAttributes resAttr = resource.getResourceAttributes(); if (resAttr.isReadOnly()) { resAttr.setReadOnly(false); resource.setResourceAttributes(resAttr); } IResource[] members = ((IContainer) resource).members(); List<IFile> files = new ArrayList<IFile>(members.length); for (IResource member : members) { if (member instanceof IFile && member.getResourceAttributes().isReadOnly()) { files.add((IFile) member); } } if (files.size() > 0) { IFile[] filesToValidate = files.toArray(new IFile[files.size()]); IStatus result = resource.getWorkspace().validateEdit(filesToValidate, null); if (!result.isOK()) throw new CoreException(result); } } } } }