/******************************************************************************* * Copyright © 2005, 2013 IBM 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: * IBM Corporation - initial API and implementation * *******************************************************************************/ package org.eclipse.edt.ide.core.utils; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.Set; import java.util.Stack; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.Status; import org.eclipse.edt.ide.core.internal.model.EGLModelManager; import org.eclipse.edt.ide.core.internal.model.IProjectsChangedListener; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; /** * * File Format * * <resource name="a"> * <storedValue key="xyz", value="123"/> * <resource name="b"> * <resource name="c"> * <storedValue key="lmn", value="456"/> * </resource> * </resource> * </resource> * * @author svihovec */ public class ResourceValueStoreUtility implements IResourceChangeListener, IProjectsChangedListener { private final String RESOURCE_ELEMENT = "resource"; //$NON-NLS-1$ private final String STORED_VALUE_ELEMENT = "storedValue"; //$NON-NLS-1$ private final String NAME_ATTRIBUTE = "name"; //$NON-NLS-1$ private final String KEY_ATTRIBUTE = "key"; //$NON-NLS-1$ private final String VALUE_ATTRIBUTE = "value"; //$NON-NLS-1$ // Thoughts //- figure out how to store portions of a file at one time - by folder // would have to potentially read the whole file more than once to find more than one folder at a time // would have to be able to write out only segments to a file // users will be doing more gets than sets // users will probably be doing gets from same set of folders over and over //- need to handle the case where a user modified the store file while we were using it, or if its format is bad. //- Is there ever a situation where I can get an update on a resource that has already been removed? // - check a resource for existance before updating the store //- do all work on a background thread or using jobs public class ResourceValueStoreNode { private String name = null; private HashMap valuesMap = new HashMap(); private ResourceValueStoreNode parent = null; private ArrayList children = new ArrayList(); public ResourceValueStoreNode(String name){ this.name = name; } public void setValue(QualifiedName key, String value){ valuesMap.put(key, value); } public String getValue(QualifiedName key){ return (String)valuesMap.get(key); } public String getName(){ return name; } public ResourceValueStoreNode[] getChildren(){ return (ResourceValueStoreNode[])children.toArray(new ResourceValueStoreNode[children.size()]); } public QualifiedName[] getStoredValueKeys(){ Set valueKeys = valuesMap.keySet(); return (QualifiedName[])valueKeys.toArray(new QualifiedName[valueKeys.size()]); } /** * @return Returns the parent. */ public ResourceValueStoreNode getParent() { return parent; } public void setParent(ResourceValueStoreNode parent){ this.parent = parent; } public void addChild(ResourceValueStoreNode child){ this.children.add(child); } public void setName(String name) { this.name = name; } public HashMap getValuesMap() { return valuesMap; } } private class ResourceValueStoreDefaultHandler extends DefaultHandler{ private Stack pathStack = new Stack(); private IPath currentPath = null; private ResourceValueStore valueStore; public ResourceValueStoreDefaultHandler(ResourceValueStore store){ this.valueStore = store; } public void startElement(String namespaceURI, String localName, String rawName, Attributes atts) { // If we have a resource, create a new resource node, using the current node as the parent if(rawName.equals(RESOURCE_ELEMENT)){ String name = atts.getValue(NAME_ATTRIBUTE); IPath newPath = new Path(name); if(currentPath != null){ newPath = currentPath.append(name); pathStack.push(currentPath); } currentPath = newPath; } // If we have a storedValue, add the value to the current node else if(rawName.equals(STORED_VALUE_ELEMENT)){ if(currentPath != null){ QualifiedName qName = createQualifiedName(atts.getValue(KEY_ATTRIBUTE)); String value = atts.getValue(VALUE_ATTRIBUTE); valueStore.setValue(currentPath.makeAbsolute(), qName, value); } } } public void endElement(String namespaceURI, String localName, String rawName) { // at the end of a resource, set the current value to the last value on the stack if applicable if(rawName.equals(RESOURCE_ELEMENT)){ if(pathStack.size() > 0){ currentPath = (IPath)pathStack.pop(); }else{ currentPath = null; } } } private QualifiedName createQualifiedName(String name){ int colonLocation = name.indexOf(":"); //$NON-NLS-1$ String qualifier = null; String localName = null; if(colonLocation != -1){ qualifier = name.substring(0, colonLocation); } localName = name.substring(colonLocation + 1, name.length()); return new QualifiedName(qualifier, localName); } } public class ResourceValueStoreFileWriter { StringBuffer output = new StringBuffer(); int tabCount = 0; private IFile outputFile; private String xmlEncode = ""; public ResourceValueStoreFileWriter(IFile outputFile){ this.outputFile = outputFile; } public void writeStart(String xmlEncoding){ this.xmlEncode = xmlEncoding; output.append("<?xml version=\"1.0\" encoding=\"" + xmlEncoding + "\"?>\n"); } public String getOutputString() { return output.toString(); } public void writeEnd(){ if(outputFile != null){ ByteArrayInputStream inputStream = null; try { inputStream = new ByteArrayInputStream(getOutputString().getBytes(xmlEncode)); if(!outputFile.exists()){ outputFile.create(inputStream, true, null); }else{ outputFile.setContents(inputStream, true, false, null); } } catch (CoreException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } public void writeResourceStart(String name){ outputTabs(tabCount); output.append("<"); //$NON-NLS-1$ output.append(RESOURCE_ELEMENT); output.append(" "); //$NON-NLS-1$ output.append(NAME_ATTRIBUTE); output.append("=\""); //$NON-NLS-1$ output.append(name); output.append("\">\n"); //$NON-NLS-1$ tabCount++; } public void writeResourceEnd(){ tabCount--; outputTabs(tabCount); output.append("</"); //$NON-NLS-1$ output.append(RESOURCE_ELEMENT); output.append(">\n"); //$NON-NLS-1$ } public void writeStoredValue(String key, String value){ if (value != null) { outputTabs(tabCount); output.append("<"); //$NON-NLS-1$ output.append(STORED_VALUE_ELEMENT); output.append(" "); //$NON-NLS-1$ output.append(KEY_ATTRIBUTE); output.append("=\""); //$NON-NLS-1$ output.append(key); output.append("\" "); //$NON-NLS-1$ output.append(VALUE_ATTRIBUTE); output.append("=\""); //$NON-NLS-1$ output.append(value); output.append("\"/>\n"); //$NON-NLS-1$ } } /** * @param tabCount */ private void outputTabs(int tabCount) { for(int i=0; i<tabCount; i++){ output.append("\t"); //$NON-NLS-1$ } } } /** * A Resource Value Store maintains the mappings of Resource Value Store nodes to resource paths. * * @author svihovec */ public class ResourceValueStore { private HashMap resourcesToNodes = new HashMap(); private IProject project = null; private String encode = ""; /** * @param project */ public ResourceValueStore(IProject project) { this.project = project; } public String getEncode() { return encode; } public void setEncode(String encode) { this.encode = encode; } private ResourceValueStoreNode getNode(IResource resource) { return (ResourceValueStoreNode)resourcesToNodes.get(resource.getFullPath()); } private void removeNode(IResource resource) { resourcesToNodes.remove(resource.getFullPath()); } private void addNode(IResource resource, ResourceValueStoreNode node) { resourcesToNodes.put(resource.getFullPath(), node); } public String getValue(IResource resource, QualifiedName key){ String result = null; ResourceValueStoreNode node = getNode(resource); if(node != null){ result = node.getValue(key); } return result; } public void setValue(IResource resource, QualifiedName key, String value){ setValue(resource.getFullPath(), key, value); } public void setValue(IPath path, QualifiedName key, String value){ ResourceValueStoreNode node = (ResourceValueStoreNode)resourcesToNodes.get(path); if(node == null){ node = createNode(path); } node.setValue(key, value); } private ResourceValueStoreNode createNode(IPath path){ ResourceValueStoreNode result = null; String resourceName = path.lastSegment(); if(path.segmentCount() > 1){ IPath parentPath = path.removeLastSegments(1); ResourceValueStoreNode parentNode = (ResourceValueStoreNode)resourcesToNodes.get(parentPath); if(parentNode == null){ parentNode = createNode(parentPath); } if(parentNode != null){ result = new ResourceValueStoreNode(resourceName); result.setParent(parentNode); parentNode.addChild(result); } }else{ result = new ResourceValueStoreNode(resourceName); } if(result != null){ resourcesToNodes.put(path, result); } return result; } public void write(ResourceValueStoreFileWriter writer){ ResourceValueStoreNode node = (ResourceValueStoreNode)resourcesToNodes.get(project.getFullPath()); if(node != null){ writer.writeStart(getEncode()); writeResource(writer, node); writer.writeEnd(); } } private void writeResource(ResourceValueStoreFileWriter writer, ResourceValueStoreNode node){ writer.writeResourceStart(node.getName()); writeStoredValues(writer, node); ResourceValueStoreNode[] children = node.getChildren(); for (int i = 0; i < children.length; i++) { ResourceValueStoreNode child = children[i]; writeResource(writer, child); } writer.writeResourceEnd(); } private void writeStoredValues(ResourceValueStoreFileWriter writer, ResourceValueStoreNode node){ QualifiedName[] keys = node.getStoredValueKeys(); for (int i = 0; i < keys.length; i++) { QualifiedName key = keys[i]; writer.writeStoredValue(key.toString(), node.getValue(key)); } } public void setProject(IProject newProject) { if (project != null) { ResourceValueStoreNode node = getNode(project); if (node != null) { node.setName(newProject.getName()); removeNode(project); addNode(newProject, node); } } this.project = newProject; } public HashMap getResourcesToNodes() { return resourcesToNodes; } } private static final ResourceValueStoreUtility INSTANCE = new ResourceValueStoreUtility(); private static final String RESOURCE_VALUE_STORE_FILE_NAME = ".eglproject"; //$NON-NLS-1$ private HashMap resourceValueStores = new HashMap(); private ArrayList resourceStoreLRUList = new ArrayList(); private static final int MAX_RESOURCE_STORES = 2; private static SAXParser parser = null; // one parser for multiple projects private ResourceValueStoreUtility(){ EGLModelManager.getEGLModelManager().deltaProcessor.addProjectsChangedListener(this); } private SAXParser initParser(){ SAXParser result = null; SAXParserFactory factory = SAXParserFactory.newInstance(); try { parser = factory.newSAXParser(); } catch (ParserConfigurationException e) { } catch (SAXException e) { } return result; } public static final ResourceValueStoreUtility getInstance(){ return INSTANCE; } public synchronized ResourceValueStore getNewResourceValueStore(IProject project){ return readStore(project, true); } public synchronized ResourceValueStore getResourceValueStore(IProject project){ return getResourceValueStore(project, true); } public synchronized ResourceValueStore getResourceValueStore(IProject project, boolean forceMigration){ ResourceValueStore valueStore = (ResourceValueStore)resourceValueStores.get(project); if(valueStore == null){ if(resourceStoreLRUList.size() + 1 > MAX_RESOURCE_STORES){ resourceValueStores.remove(resourceStoreLRUList.remove(resourceStoreLRUList.size() -1)); } valueStore = readStore(project, forceMigration); resourceValueStores.put(project, valueStore); }else{ resourceStoreLRUList.remove(project); } resourceStoreLRUList.add(0, project); return valueStore; } public String getValue(IResource resource, QualifiedName key) throws CoreException{ return getValue(resource, key, true); } public String getValue(IResource resource, QualifiedName key, boolean forceMigration) throws CoreException{ validateParameters(resource, key); return getResourceValueStore(resource.getProject(), forceMigration).getValue(resource, key); } /** * Fix for RATLC00317502. Delete the project in the workspace, the EGL source code will * not be migrated. Read the project information(.eglproject) from file instead of memory cache. * @param resource * @param key * @return * @throws CoreException */ public String getValueWithoutCache(IResource resource, QualifiedName key) throws CoreException{ validateParameters(resource, key); IProject project = resource.getProject(); ResourceValueStore valueStore = null; valueStore = readStore(project, true); return valueStore.getValue(resource, key); } public void setValue(IResource resource, QualifiedName key, String value) throws CoreException { validateParameters(resource, key); IProject project = resource.getProject(); ResourceValueStore valueStore = getResourceValueStore(project, true); valueStore.setValue(resource, key, value); writeStore(project, valueStore); } /** * @param resource * @param key */ private void validateParameters(IResource resource, QualifiedName key) throws CoreException { if(resource.exists()){ if(resource.getType() == IResource.PROJECT){ if(!((IProject)resource).isOpen()){ // TODO Set plugin id // TODO Set plugin specific status code throw new CoreException(new Status(IStatus.ERROR, "myplugin", 0, "Project is closed", null)); //$NON-NLS-1$ //$NON-NLS-2$ } } }else{ // TODO Set plugin id // TODO Set plugin specific status code throw new CoreException(new Status(IStatus.ERROR, "myplugin", 0, "Resource does not exist", null)); //$NON-NLS-1$ //$NON-NLS-2$ } if(key == null){ // TODO Set plugin id // TODO Set plugin specific status code throw new CoreException(new Status(IStatus.ERROR, "myplugin", 0, "Key is null", null)); //$NON-NLS-1$ //$NON-NLS-2$ } } private ResourceValueStore readStore(IProject project, boolean forceMigration){ ResourceValueStore valueStore = new ResourceValueStore(project); try { String encode = "UTF-8"; if(forceMigration) encode = migrateEGLProjectFile2UTF8(project); IFile resourceFile = project.getFile(RESOURCE_VALUE_STORE_FILE_NAME); if(resourceFile.exists()){ ResourceValueStoreDefaultHandler handler = new ResourceValueStoreDefaultHandler(valueStore); if(parser == null){ initParser(); } InputStream inputStream = new BufferedInputStream(resourceFile.getContents(true)); try{ parser.parse(inputStream, handler); }finally{ inputStream.close(); } } else { // getOldMetaData(project, valueStore); } valueStore.setEncode(encode); } catch (SAXException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (CoreException e) { // TODO Auto-generated catch block e.printStackTrace(); } return valueStore; } /** * For the WI 29715, the migration process does not work correctly with current encoding of .eglproject. * Migrate the contents of .eglproject to the UTF-8 encoding and save the contents to * .eglproject. * @param project * @throws CoreException */ private String migrateEGLProjectFile2UTF8(IProject project) throws CoreException { String encode = "UTF-8"; IFile resourceFile = project.getFile(RESOURCE_VALUE_STORE_FILE_NAME); BufferedInputStream inputStream; try { if(resourceFile.exists()) { inputStream = new BufferedInputStream(resourceFile.getContents(true)); byte[] bytesContents = org.eclipse.edt.ide.core.internal.model.util.Util .getInputStreamAsByteArray(inputStream, -1); inputStream.close(); String reg = "<\\s*\\?\\s*xml\\s*version\\s*=.*\\?\\s*>"; Pattern pattern = Pattern.compile(reg, Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(new String(bytesContents)); //Found the xml version and encoding definition if(matcher.find()){ String decl = matcher.group(); reg = "encoding\\s*=\\s*\".*\""; pattern = Pattern.compile(reg, Pattern.CASE_INSENSITIVE); matcher = pattern.matcher(decl); if(matcher.find()) { pattern = Pattern.compile("\".*\"", Pattern.CASE_INSENSITIVE); matcher = pattern.matcher(matcher.group()); if(matcher.find()){ encode = matcher.group().replaceAll("\"", "").trim(); } } return encode; } //Not find, then migrate the contents to the UTF-8 StringBuffer temp = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); temp.append(new String(bytesContents)); IFile outputFile = project.getFile(RESOURCE_VALUE_STORE_FILE_NAME); outputFile.setContents(new ByteArrayInputStream(temp.toString().getBytes("UTF-8")), true, false, null); outputFile.setCharset("UTF-8", null); } } catch (Exception e) { e.printStackTrace(); } return encode; } // private void getOldMetaData(IProject project, ResourceValueStore valueStore) { //Migrate the data in the V6.x meta data to the .eglProjectInfo file //This code can probably be deleted in RAD V8.0 or so // String[] targetPartWrapper = getOldDefaultBuildDescriptor(IEGLBuildDescriptorLocator.RUNTIME_DEFAULT_BUILD_DESCRIPTOR_TYPE, project); // String[] debugPartWrapper = getOldDefaultBuildDescriptor(IEGLBuildDescriptorLocator.DEBUG_DEFAULT_BUILD_DESCRIPTOR_TYPE, project); // if (targetPartWrapper != null) { // valueStore.setValue(project, new QualifiedName(null, EGLProjectInfoUtility.DEFAULT_BUILD_DESCRIPTOR_TARGET_NAME), targetPartWrapper[0]); // valueStore.setValue(project, new QualifiedName(null, EGLProjectInfoUtility.DEFAULT_BUILD_DESCRIPTOR_TARGET_PATH), targetPartWrapper[1]); // } // if (debugPartWrapper != null) { // valueStore.setValue(project, new QualifiedName(null, EGLProjectInfoUtility.DEFAULT_BUILD_DESCRIPTOR_DEBUG_NAME), debugPartWrapper[0]); // valueStore.setValue(project, new QualifiedName(null, EGLProjectInfoUtility.DEFAULT_BUILD_DESCRIPTOR_DEBUG_PATH), debugPartWrapper[1]); // } // if (targetPartWrapper != null || debugPartWrapper != null) // writeStore(project, valueStore); // valueStore.setValue(project, EGLBasePlugin.VALUESTOREKEY_EGLFEATURE, Integer.toString(EGLBasePlugin.EGLFEATURE_JASPER_MASK)); // } // public String[] getOldDefaultBuildDescriptor(int defaultType, IResource resource) { // // QualifiedName EGL_DEFAULT_RUNTIME_BD_NAME_KEY = new QualifiedName( // "com.ibm.etools.egl.internal.utilities", //$NON-NLS-1$ // "EGLDefaultRuntimeBuildDescriptorNameKey"); //$NON-NLS-1$ // // QualifiedName EGL_DEFAULT_RUNTIME_BD_PATH_KEY = new QualifiedName( // "com.ibm.etools.egl.internal.utilities", //$NON-NLS-1$ // "EGLDefaultRuntimeBuildDescriptorPathKey"); //$NON-NLS-1$ // // QualifiedName EGL_DEFAULT_DEBUG_BD_NAME_KEY = new QualifiedName( // "com.ibm.etools.egl.internal.utilities", //$NON-NLS-1$ // "EGLDefaultDebugBuildDescriptorNameKey"); //$NON-NLS-1$ // // QualifiedName EGL_DEFAULT_DEBUG_BD_PATH_KEY = new QualifiedName( // "com.ibm.etools.egl.internal.utilities", //$NON-NLS-1$ // "EGLDefaultDebugBuildDescriptorPathKey"); //$NON-NLS-1$ // // String[] result = null; // String bdName = null; // String bdPath = null; // // // get the default bd string for this type and resource // if (resource != null) { // try { // // based on the default type, get the correct property // switch (defaultType) { // case IEGLBuildDescriptorLocator.RUNTIME_DEFAULT_BUILD_DESCRIPTOR_TYPE: // bdName = resource // .getPersistentProperty(EGL_DEFAULT_RUNTIME_BD_NAME_KEY); // bdPath = resource // .getPersistentProperty(EGL_DEFAULT_RUNTIME_BD_PATH_KEY); // break; // case IEGLBuildDescriptorLocator.DEBUG_DEFAULT_BUILD_DESCRIPTOR_TYPE: // bdName = resource // .getPersistentProperty(EGL_DEFAULT_DEBUG_BD_NAME_KEY); // bdPath = resource // .getPersistentProperty(EGL_DEFAULT_DEBUG_BD_PATH_KEY); // break; // default: // break; // } // // //need to return a null result instead of an EGLPartWrapper // // with empty strings // if (bdName != null && bdName.length() == 0) // bdName = null; // if (bdPath != null && bdPath.length() == 0) // bdPath = null; // // if (bdName != null && bdPath != null) // result = new String[] {bdName, bdPath}; // } catch (CoreException e) { // } // } // // return result; // } public void writeStore(IProject project, ResourceValueStore valueStore){ valueStore.write(new ResourceValueStoreFileWriter(project.getFile(RESOURCE_VALUE_STORE_FILE_NAME))); } /** * For testing purposes only * */ public void clearCache(){ resourceStoreLRUList.clear(); resourceValueStores.clear(); } /* (non-Javadoc) * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) */ public void resourceChanged(IResourceChangeEvent event) { //TODO // how to handle multiple threaded requests? (i.e. a change is fired, and then a delete, but the delete gets there first) // - have all of the requests get to this tool and than have this tool background job them? // - check all resources being changed for existance before processing! // we are only interested in POST_CHANGE events if (event.getType() != IResourceChangeEvent.POST_CHANGE) return; IResourceDelta rootDelta = event.getDelta(); final ArrayList removed = new ArrayList(); IResourceDeltaVisitor visitor = new IResourceDeltaVisitor() { public boolean visit(IResourceDelta delta) { // TODO How are moves handles, will I get a removed, and added, or do I have to check the flags for moved? // if I get a moved, can I be smarter about preserving properties? //only interested in deleted resources (not added or changed) if (delta.getKind() != IResourceDelta.REMOVED) return true; IResource resource = delta.getResource(); removed.add(resource); return true; } }; try { rootDelta.accept(visitor); } catch (CoreException e) { //TODO open error dialog with syncExec or print to plugin log file } //nothing more to do if there were no removed files if (removed.size() != 0){ // TODO deal with the removed files } } public void projectsChanged(IProject[] projects) { resourceValueStores.clear(); } }