/**
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on Oct 21, 2006
*
* @author Gergely Kis
*/
package org.python.pydev.plugin.nature;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
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.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.log.Log;
import org.python.pydev.editor.codecompletion.revisited.ProjectModulesManager;
import org.python.pydev.shared_core.io.FileUtils;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.string.StringUtils;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
/**
* This class stores PythonNature and PythonPathNature properties inside the project in a file instead of persistent
* properties. This allows PYTHONPATH and Python project version to be checked in into version control systems.
*
* @author Gergely Kis <gergely.kis@gmail.com>
*
*/
class PythonNatureStore implements IResourceChangeListener, IPythonNatureStore {
private final static String STORE_FILE_NAME = ".pydevproject";
private volatile IProject project = null;
/**
* We have an IFile, but the access is mostly through the actual File (because we don't want to deal with any refresh
* from eclipse and its caching mechanism)
*/
private volatile IFile xmlFile = null;
private volatile String lastLoadedContents = null;
/**
* Whether the file has already been loaded
*/
private volatile boolean loaded = false;
/**
* This is the dom document that is used to manipulate the xml info.
*/
private volatile Document document = null;
private static final boolean TRACE_PYTHON_NATURE_STORE = false;
private StringBuffer indent = new StringBuffer();
private volatile boolean inInit;
public String getLastLoadedContents() {
return lastLoadedContents;
}
private void traceFunc(String func, Object... args) {
if (TRACE_PYTHON_NATURE_STORE) {
FastStringBuffer buf = new FastStringBuffer(func, 128);
for (Object o : args) {
buf.appendObject(o);
}
func = buf.toString();
if (!func.startsWith("END ")) {
System.out.println(indent + func);
indent.append(" ");
} else {
indent.delete(0, 2);
System.out.println(indent + func);
}
}
}
/* (non-Javadoc)
* @see org.python.pydev.plugin.nature.IPythonNatureStore#setProject(org.eclipse.core.resources.IProject)
*/
@Override
public void setProject(IProject project) {
synchronized (this) {
if (project == null) {
if (this.project == null) {
return; // already de-configured
}
//removing configurations...
traceFunc("setProject: null");
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
this.project = null;
this.xmlFile = null;
this.document = null;
} else {
traceFunc("setProject - ", project.getName());
try {
this.project = project;
this.xmlFile = project.getFile(STORE_FILE_NAME);
try {
loadFromFile();
} catch (CoreException e) {
throw new RuntimeException("Error loading project: " + project, e);
}
if (!ProjectModulesManager.IN_TESTS) {
project.getWorkspace().addResourceChangeListener(this);
}
} finally {
loaded = true;
}
traceFunc("END setProject - ", project.getName());
}
}
}
/**
* @param function the function that is checking for load
*/
private synchronized void checkLoad(String function) {
if (!loaded) {
Throwable e = new RuntimeException(StringUtils.format("%s still not loaded and '%s' already called.",
xmlFile, function));
Log.log(e);
}
}
/* (non-Javadoc)
* @see org.python.pydev.plugin.nature.IPythonNatureStore#getPathProperty(org.eclipse.core.runtime.QualifiedName)
*/
@Override
public synchronized String getPathProperty(QualifiedName key) throws CoreException {
if (this.project == null) {
return "";
}
checkLoad("getPathProperty");
String ret = getPathStringFromArray(getPathPropertyFromXml(key));
traceFunc("END getPathProperty - ", key, ret);
return ret;
}
/* (non-Javadoc)
* @see org.python.pydev.plugin.nature.IPythonNatureStore#getMapProperty(org.eclipse.core.runtime.QualifiedName)
*/
@Override
public synchronized Map<String, String> getMapProperty(QualifiedName key) throws CoreException {
if (this.project == null) {
return null;
}
checkLoad("getMapProperty");
String[] keyAndValues = null;
synchronized (this) {
try {
Node propertyNode = findPropertyNodeInXml("pydev_variables_property", key);
if (propertyNode != null) {
keyAndValues = getChildValuesWithType(propertyNode, "key", "value");
}
} catch (Exception e) {
traceFunc("END getMapProperty (EXCEPTION)");
IStatus status = new Status(IStatus.ERROR, "PythonNatureStore", -1, e.toString(), e);
throw new CoreException(status);
}
}
Map<String, String> ret = null;
if (keyAndValues != null) {
ret = getMapStringFromArray(keyAndValues);
}
traceFunc("END getMapProperty - ", key, ret);
return ret;
}
/* (non-Javadoc)
* @see org.python.pydev.plugin.nature.IPythonNatureStore#setPathProperty(org.eclipse.core.runtime.QualifiedName, java.lang.String)
*/
@Override
public synchronized void setPathProperty(QualifiedName key, String value) throws CoreException {
checkLoad("setPathProperty");
setPathPropertyToXml(key, getArrayFromPathString(value), true);
}
@Override
public synchronized void setMapProperty(QualifiedName key, Map<String, String> value) throws CoreException {
checkLoad("setMapProperty");
traceFunc("setMapProperty");
synchronized (this) {
try {
boolean store = true;
Node oldChild = findPropertyNodeInXml("pydev_variables_property", key);
if (oldChild != null && (value == null || value.size() == 0)) {
getRootNodeInXml().removeChild(oldChild);
} else if (value != null && value.size() > 0) {
Node property = document.createElement("pydev_variables_property");
Node propertyName = document.createAttribute("name");
propertyName.setNodeValue(getKeyString(key));
property.getAttributes().setNamedItem(propertyName);
Set<Entry<String, String>> entrySet = value.entrySet();
for (Entry<String, String> entry : entrySet) {
Node childKey = document.createElement("key");
setTextContent(entry.getKey(), childKey);
property.appendChild(childKey);
Node childValue = document.createElement("value");
setTextContent(entry.getValue(), childValue);
property.appendChild(childValue);
}
if (oldChild == null) {
// The property is not in the file and we need to set it
getRootNodeInXml().appendChild(property);
} else {
// Replace it
getRootNodeInXml().replaceChild(property, oldChild);
}
} else {
store = false;
}
if (store) {
doStore();
}
} catch (Exception e) {
traceFunc("END setMapProperty (EXCEPTION)");
IStatus status = new Status(IStatus.ERROR, "PythonNatureStore", -1, e.toString(), e);
throw new CoreException(status);
}
}
traceFunc("END setMapProperty");
}
/**
* Loads the Xml representation of the PythonNature properties from the project resource. If the project resource does not exist then an empty representation is created, and its storage is
* requested in the project folder.
*
* @return true if some change has actually happened in the file and false otherwise
* @throws CoreException
*/
private synchronized boolean loadFromFile() throws CoreException {
if (this.project == null) {
return false; //not configured...
}
traceFunc("loadFromFile");
DocumentBuilder parser;
try {
parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new RuntimeException(e); //What can we do about that?
}
File file = getRawXmlFileLocation();
try {
if (file == null || !file.exists()) {
if (document != null) {
// Someone removed the project descriptor, store it from the memory model
doStore();
return true;
} else {
// The document never existed (create the default)
createAndSetInMemoryDocument(parser);
doStore();
return true;
}
} else {
String fileContents = FileUtils.getFileContents(file);
if (lastLoadedContents != null && fileContents.equals(lastLoadedContents)) {
return false;
}
lastLoadedContents = fileContents;
try {
document = parser.parse(new ByteArrayInputStream(fileContents.getBytes()));
} catch (Exception e) {
handleProblemInXmlDocument(parser, file, e);
}
return true;
}
} catch (Exception e) {
handleProblemInXmlDocument(parser, file, e);
}
traceFunc("END loadFromFile");
return false;
}
/**
* This method should be called if we have a problem parsing the xml file.
* It creates a backup of the original file and creates a new document to be used.
*/
private void handleProblemInXmlDocument(DocumentBuilder parser, File file, Exception e) throws CoreException {
Log.log("Error loading contents from .pydevproject: " + file, e);
try {
FileUtils.createBackupFile(file);
} catch (Exception e1) {
Log.log("Error creating backup for: " + file, e);
}
createAndSetInMemoryDocument(parser);
doStore();
}
/**
* Creates a new xml document in memory
* @return
*/
private void createAndSetInMemoryDocument(DocumentBuilder parser) throws CoreException {
document = parser.newDocument();
ProcessingInstruction version = document.createProcessingInstruction("eclipse-pydev", "version=\"1.0\""); //$NON-NLS-1$ //$NON-NLS-2$
document.appendChild(version);
Element configRootElement = document.createElement("pydev_project");
document.appendChild(configRootElement);
migrateProperty(PythonNature.getPythonProjectVersionQualifiedName());
migratePath(PythonPathNature.getProjectSourcePathQualifiedName());
migratePath(PythonPathNature.getProjectExternalSourcePathQualifiedName());
}
/**
* @return the actual file from the IFile we have
*/
private File getRawXmlFileLocation() {
IPath rawLocation = xmlFile.getRawLocation();
File file = null;
if (rawLocation != null) {
file = rawLocation.toFile();
}
return file;
}
private synchronized void migrateProperty(QualifiedName key) throws CoreException {
traceFunc("migrateProperty");
synchronized (this) {
// Nothing found, try to migrate from persistent property
String propertyVal = project.getPersistentProperty(key);
if (propertyVal != null) {
setPropertyToXml(key, propertyVal, false);
project.setPersistentProperty(key, (String) null);
}
}
traceFunc("END migrateProperty");
}
private synchronized void migratePath(QualifiedName key) throws CoreException {
traceFunc("migratePath");
// Try to migrate from persistent property
String[] propertyVal = getArrayFromPathString(project.getPersistentProperty(key));
if (propertyVal != null) {
// set in the xml
setPathPropertyToXml(key, propertyVal, false);
// and remove from the project
project.setPersistentProperty(key, (String) null);
}
traceFunc("END migratePath");
}
/**
* Get the root node of the project description
*
* @return the root Node object
* @throws MisconfigurationException
* @throws CoreException if root node is not present
*/
private synchronized Node getRootNodeInXml() throws MisconfigurationException {
traceFunc("getRootNodeInXml");
if (document == null) {
throw new MisconfigurationException("Found null XML document. Please check if " + this.xmlFile
+ " is a valid XML file.");
}
NodeList nodeList = document.getElementsByTagName("pydev_project");
Node ret = null;
if (nodeList != null && nodeList.getLength() > 0) {
ret = nodeList.item(0);
}
traceFunc("END getRootNodeInXml -- ", ret);
if (ret != null) {
return ret;
}
throw new RuntimeException(StringUtils.format("Error. Unable to get the %s tag by its name. Project: %s",
"pydev_project", project));
}
/**
* Assemble a string representation of a QualifiedName typed key
*
* @param key
* @return the assembled string key representation
*/
private synchronized String getKeyString(QualifiedName key) {
traceFunc("getKeyString");
String keyString = key.getQualifier() != null ? key.getQualifier() : "";
String ret = keyString + "." + key.getLocalName();
traceFunc("END getKeyString");
return ret;
}
/**
* Finds a property node as a direct child of the root node with the specified type and key.
*
* @param type
* @param key
* @return The property node or null if a node with the supplied key and type cannot be found.
* @throws MisconfigurationException
* @throws CoreException
*/
private synchronized Node findPropertyNodeInXml(String type, QualifiedName key) throws MisconfigurationException {
traceFunc("findPropertyNodeInXml");
Node root = getRootNodeInXml();
NodeList childNodes = root.getChildNodes();
if (childNodes != null && childNodes.getLength() > 0) {
String keyString = getKeyString(key);
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
if (child.getNodeName().equals(type)) {
NamedNodeMap attrs = child.getAttributes();
if (attrs != null && attrs.getLength() > 0) {
Node namedItem = attrs.getNamedItem("name");
if (namedItem != null) {
String name = namedItem.getNodeValue();
if (name != null && name.equals(keyString)) {
traceFunc("END findPropertyNodeInXml - ", child);
return child;
}
}
}
}
}
}
traceFunc("END findPropertyNodeInXml (null)");
return null;
}
/**
* Returns the text contents of a nodes' children. The children shall have the specified type.
*
* @param node
* @param type
* @return the array of strings with the text contents or null if the node has no children.
*/
private String[] getChildValuesWithType(Node node, String... type) {
traceFunc("getChildValuesWithType");
NodeList childNodes = node.getChildNodes();
if (childNodes != null && childNodes.getLength() > 0) {
List<String> result = new ArrayList<String>();
for (int i = 0; i < childNodes.getLength(); i++) {
Node child = childNodes.item(i);
String nodeName = child.getNodeName();
for (String t : type) {
if (nodeName.equals(t)) {
result.add(getTextContent(child));
break;//after added, we can break the inner for.
}
}
}
String[] retval = new String[result.size()];
traceFunc("END getChildValuesWithType");
return result.toArray(retval);
}
traceFunc("END getChildValuesWithType (null)");
return null;
}
/**
* Add children to a node with specified type and text contents. For each values array element a new child is created.
*
* @param node
* @param type
* @param values
*/
private void addChildValuesWithType(Node node, String type, String[] values) {
traceFunc("addChildValuesWithType");
assert (node != null);
assert (values != null);
assert (type != null);
for (int i = 0; i < values.length; i++) {
Node child = document.createElement(type);
setTextContent(values[i], child);
node.appendChild(child);
}
traceFunc("END addChildValuesWithType");
}
/**
* Convert an array of path strings to a single string separated by | characters.
*
* @param pathArray
* @return the assembled string of paths or null if the input was null
*/
private String getPathStringFromArray(String[] pathArray) {
traceFunc("getPathStringFromArray");
if (pathArray != null) {
FastStringBuffer s = new FastStringBuffer();
for (int i = 0; i < pathArray.length; i++) {
if (i > 0) {
s.append('|');
}
s.append(pathArray[i]);
}
traceFunc("END getPathStringFromArray");
return s.toString();
}
traceFunc("END getPathStringFromArray (null)");
return null;
}
/**
* Convert an array of path strings to a Map<String, String>
* @return the assembled string of paths or null if the input was null.
*/
private Map<String, String> getMapStringFromArray(String[] pathArray) {
traceFunc("getMapStringFromArray");
if (pathArray != null) {
HashMap<String, String> ret = new HashMap<String, String>();
for (int i = 0; i < pathArray.length - 1; i += 2) {
//loop on 0, 2, 4... we stop at -1 to prevent against uneven (which are actually wrong) arrays.
String key = pathArray[i];
String val = pathArray[i + 1];
ret.put(key, val);
}
return ret;
}
traceFunc("END getMapStringFromArray (null)");
return null;
}
/**
* Convert a single string of paths separated by | characters to an array of strings.
*
* @param pathString
* @return the splitted array of strings or null if the input was null
*/
private String[] getArrayFromPathString(String pathString) {
traceFunc("getArrayFromPathString");
if (pathString != null) {
traceFunc("END getArrayFromPathString");
return pathString.split("\\|");
}
traceFunc("END getArrayFromPathString (null)");
return null;
}
/* (non-Javadoc)
* @see org.python.pydev.plugin.nature.IPythonNatureStore#getPropertyFromXml(org.eclipse.core.runtime.QualifiedName)
*/
@Override
public synchronized String getPropertyFromXml(QualifiedName key) {
if (this.project == null) {
return "";
}
traceFunc("getPropertyFromXml - ", key);
synchronized (this) {
checkLoad("getPropertyFromXml");
try {
Node propertyNode = findPropertyNodeInXml("pydev_property", key);
if (propertyNode != null) {
String ret = getTextContent(propertyNode);
traceFunc("END getPropertyFromXml -- ", ret);
return ret;
}
traceFunc("END getPropertyFromXml (null)");
return null;
} catch (Exception e) {
traceFunc("END getPropertyFromXml (EXCEPTION)");
throw new RuntimeException("Error on document:" + document + " project:" + project, e);
}
}
}
/* (non-Javadoc)
* @see org.python.pydev.plugin.nature.IPythonNatureStore#setPropertyToXml(org.eclipse.core.runtime.QualifiedName, java.lang.String, boolean)
*/
@Override
public synchronized void setPropertyToXml(QualifiedName key, String value, boolean store) throws CoreException {
traceFunc(StringUtils.format("setPropertyToXml key:%s value:%s store:%s", key, value, store));
synchronized (this) {
if (store) {
checkLoad("setPropertyToXml");
}
try {
Node child = findPropertyNodeInXml("pydev_property", key);
if (child != null) {
if (value == null) {
// remove child from file
getRootNodeInXml().removeChild(child);
} else {
setTextContent(value, child);
}
} else if (value != null) {
// The property is not in the file and we need to set it
Node property = document.createElement("pydev_property");
Node propertyName = document.createAttribute("name");
propertyName.setNodeValue(getKeyString(key));
property.getAttributes().setNamedItem(propertyName);
setTextContent(value, property);
getRootNodeInXml().appendChild(property);
} else {
store = false;
}
if (store) {
doStore();
}
} catch (Exception e) {
traceFunc("END setPropertyToXml (EXCEPTION)");
IStatus status = new Status(IStatus.ERROR, "PythonNatureStore", -1, e.toString(), e);
throw new CoreException(status);
}
}
traceFunc("END setPropertyToXml");
}
/**
* This function was gotten as a copy of the Node.setTextContent, because this function
* is not available in java 1.4
*/
private void setTextContent(String textContent, Node self) throws DOMException {
traceFunc("setTextContent");
// get rid of any existing children
Node child;
while ((child = self.getFirstChild()) != null) {
self.removeChild(child);
}
// create a Text node to hold the given content
if (textContent != null && textContent.length() != 0) {
self.appendChild(document.createTextNode(textContent));
}
traceFunc("END setTextContent");
}
private String getTextContent(Node self) throws DOMException {
traceFunc("getTextContent");
FastStringBuffer fBufferStr = new FastStringBuffer();
Node child = self.getFirstChild();
if (child != null) {
Node next = child.getNextSibling();
if (next == null) {
if (hasTextContent(child)) {
String nodeValue = child.getNodeValue();
if (nodeValue != null) {
traceFunc("END getTextContent - ", nodeValue);
return nodeValue;
}
}
traceFunc("END getTextContent - EMPTY");
return "";
}
fBufferStr.clear();
getTextContent(fBufferStr, self);
traceFunc("END getTextContent - ", fBufferStr);
return fBufferStr.toString();
}
traceFunc("END getTextContent - EMPTY");
return "";
}
// internal method taking a StringBuffer in parameter
private synchronized void getTextContent(FastStringBuffer buf, Node self) throws DOMException {
traceFunc("getTextContent");
synchronized (this) {
Node child = self.getFirstChild();
while (child != null) {
if (hasTextContent(child)) {
getTextContent(buf, child);
}
child = child.getNextSibling();
}
}
traceFunc("END getTextContent");
}
// internal method returning whether to take the given node's text content
private boolean hasTextContent(Node child) {
traceFunc("hasTextContent");
boolean ret = child.getNodeType() != Node.COMMENT_NODE
&& child.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE;
traceFunc("END hasTextContent ", ret);
return ret;
}
/**
* Retrieve the value of a path property from the Xml representation. If the property is not found in the Xml document, the eclipse persistent property of the same key is read and migrated to the
* xml representation.
*
* @param key
* @return the array of strings representing paths
* @throws CoreException
*/
private String[] getPathPropertyFromXml(QualifiedName key) throws CoreException {
traceFunc("getPathPropertyFromXml");
synchronized (this) {
try {
Node propertyNode = findPropertyNodeInXml("pydev_pathproperty", key);
if (propertyNode != null) {
traceFunc("END getPathPropertyFromXml");
return getChildValuesWithType(propertyNode, "path");
}
traceFunc("END getPathPropertyFromXml (null)");
return null;
} catch (Exception e) {
traceFunc("END getPathPropertyFromXml (EXCEPTION)");
IStatus status = new Status(IStatus.ERROR, "PythonNatureStore", -1, e.toString(), e);
throw new CoreException(status);
}
}
}
/**
* Store a path property in the xml document and request the storage of changes. If the paths parameter is null the property is removed from the document.
*
* @param key
* @param paths
* @throws CoreException
*/
private void setPathPropertyToXml(QualifiedName key, String[] paths, boolean store) throws CoreException {
traceFunc("setPathPropertyToXml");
synchronized (this) {
try {
Node oldChild = findPropertyNodeInXml("pydev_pathproperty", key);
if (paths != null) {
ArrayList<String> pathsList = new ArrayList<String>();
for (String p : paths) {
if (p != null && p.length() > 0) {
pathsList.add(p);
}
}
paths = pathsList.toArray(new String[0]);
}
if (oldChild != null && (paths == null || paths.length == 0)) {
getRootNodeInXml().removeChild(oldChild);
} else if (paths != null) {
Node property = document.createElement("pydev_pathproperty");
Node propertyName = document.createAttribute("name");
propertyName.setNodeValue(getKeyString(key));
property.getAttributes().setNamedItem(propertyName);
addChildValuesWithType(property, "path", paths);
if (oldChild == null) {
// The property is not in the file and we need to set it
getRootNodeInXml().appendChild(property);
} else {
// Replace it
getRootNodeInXml().replaceChild(property, oldChild);
}
} else {
store = false;
}
if (store) {
doStore();
}
} catch (Exception e) {
traceFunc("END setPathPropertyToXml (EXCEPTION)");
IStatus status = new Status(IStatus.ERROR, "PythonNatureStore", -1, e.toString(), e);
throw new CoreException(status);
}
}
traceFunc("END setPathPropertyToXml");
}
/**
* Serializes an Xml document to an array of bytes.
*
* @param doc
* @return the array of bytes representing the Xml document
* @throws IOException
* @throws TransformerException
*/
private byte[] serializeDocument(Document doc) throws IOException, TransformerException {
traceFunc("serializeDocument");
synchronized (this) {
ByteArrayOutputStream s = new ByteArrayOutputStream();
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.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 outputTarget = new StreamResult(s);
transformer.transform(source, outputTarget);
traceFunc("END serializeDocument");
return s.toByteArray();
}
}
@Override
public void resourceChanged(IResourceChangeEvent event) {
if (project == null) {
return;
}
traceFunc("resourceChanged -- ", project.getName());
if (inInit) {
traceFunc("END resourceChanged (inInit)");
return;
}
//not synched -- called when a file is changed
boolean doRebuild = false;
if (!project.isOpen()) {
traceFunc("END resourceChanged (!open)");
return; // project closed... no need to do anything if the project is closed
}
IResourceDelta eventDelta = event.getDelta();
if (eventDelta == null) {
traceFunc("END resourceChanged (eventDelta == null)");
return; //no delta here... move on
}
IResourceDelta delta = eventDelta.findMember(xmlFile.getFullPath());
if (delta != null) {
try {
if (loadFromFile()) { //it'll only return true if the contents really changed.
doRebuild = true;
}
} catch (CoreException e) {
traceFunc("END resourceChanged (EXCEPTION)");
throw new RuntimeException(e);
}
}
if (doRebuild) {
PythonNature nature = PythonNature.getPythonNature(project);
if (nature != null) {
nature.rebuildPath();
}
}
traceFunc("END resourceChanged -- rebuilt:", doRebuild);
}
/**
* This is the function that actually stores the contents of the xml into the file with the configurations.
*/
private synchronized IStatus doStore() {
if (this.project == null) {
return Status.OK_STATUS;
}
traceFunc("doStore");
if (inInit) {
traceFunc("END doStore (inInit)");
return Status.OK_STATUS;
}
synchronized (this) {
if (document == null) {
traceFunc("END doStore (document == null)");
return new Status(Status.ERROR, "PythonNatureStore.doStore", -2, "document == null",
new RuntimeException("document == null"));
}
File file = getRawXmlFileLocation();
try {
//Ok, we may receive multiple requests at once (e.g.: when updating the version and the pythonpath together), but
//as the file is pretty small, there should be no problems in writing it directly (if that proves a problem later on, we
//could have a *very* simple mechanism for saving it after some millis)
String str = new String(serializeDocument(document));
lastLoadedContents = str;
if (file == null) {
if (!ProjectModulesManager.IN_TESTS) {
//if we're not in tests, let's log this, as it'd be an error.
Log.log("Error: xml file should only be null in tests (when no workspace is available)");
}
return Status.OK_STATUS;
}
if (TRACE_PYTHON_NATURE_STORE) {
System.out.println("Writing to file: " + file + " " + str);
}
FileUtils.writeStrToFile(str, file);
} catch (Exception e) {
Log.log("Unable to write contents of file: " + file, e);
}
traceFunc("END doStore");
return Status.OK_STATUS;
}
}
@Override
public void startInit() {
inInit = true;
}
@Override
public void endInit() {
inInit = false;
doStore();
}
}