blob: d939b1e2737adc72e085089fc50887c2b8f20987 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2007 BEA Systems, Inc.
* 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:
* jgarms@bea.com - initial API and implementation
*
*******************************************************************************/
package org.eclipse.jdt.apt.core.internal.util;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
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.Status;
import org.eclipse.jdt.apt.core.internal.AptPlugin;
import org.eclipse.jdt.apt.core.internal.ExtJarFactoryContainer;
import org.eclipse.jdt.apt.core.internal.FactoryPluginManager;
import org.eclipse.jdt.apt.core.internal.VarJarFactoryContainer;
import org.eclipse.jdt.apt.core.internal.WkspJarFactoryContainer;
import org.eclipse.jdt.apt.core.internal.util.FactoryContainer.FactoryType;
import org.eclipse.jdt.apt.core.util.IFactoryPath;
import org.eclipse.jdt.core.IJavaProject;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Utility class for dealing with the factory path
*/
public final class FactoryPathUtil {
private static final String FACTORYPATH_TAG = "factorypath"; //$NON-NLS-1$
private static final String FACTORYPATH_ENTRY_TAG = "factorypathentry"; //$NON-NLS-1$
private static final String KIND = "kind"; //$NON-NLS-1$
private static final String ID = "id"; //$NON-NLS-1$
private static final String ENABLED = "enabled"; //$NON-NLS-1$
private static final String RUN_IN_BATCH_MODE = "runInBatchMode"; //$NON-NLS-1$
private static final String FACTORYPATH_FILE = ".factorypath"; //$NON-NLS-1$
// four spaces for indent
private static final String INDENT = " "; //$NON-NLS-1$
// Private c-tor to prevent construction
private FactoryPathUtil() {}
/**
* Test whether a resource is a factory path file. The criteria are
* that it is a file, it belongs to a project, it is located in the root
* of that project, and it is named ".factorypath". Note that the
* workspace-wide factorypath file does NOT meet these criteria.
* @param res any sort of IResource, or null.
* @return true if the resource is a project-specific factory path file.
*/
public static boolean isFactoryPathFile(IResource res) {
if (res == null || res.getType() != IResource.FILE || res.getProject() == null) {
return false;
}
IPath path = res.getProjectRelativePath();
if (path.segmentCount() != 1) {
return false;
}
return FACTORYPATH_FILE.equals(path.lastSegment());
}
/**
* Loads a map of factory containers from the factory path for a given
* project. If no factorypath file was found, returns null.
*/
public static Map<FactoryContainer, FactoryPath.Attributes> readFactoryPathFile(IJavaProject jproj)
throws CoreException
{
String data = null;
try {
// If project is null, use workspace-level data
if (jproj == null) {
File file = getFileForWorkspace();
if (!file.exists()) {
return null;
}
data = FileSystemUtil.getContentsOfFile(file);
}
else {
IFile ifile = getIFileForProject(jproj);
if (!ifile.exists()) {
return null;
}
data = FileSystemUtil.getContentsOfIFile(ifile);
}
}
catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, AptPlugin.PLUGIN_ID, -1, Messages.FactoryPathUtil_status_ioException, e));
}
return FactoryPathUtil.decodeFactoryPath(data);
}
/**
* Stores a map of factory containers to the factorypath file
* for a given project. If null is passed in, the factorypath file
* is deleted.
*/
public static void saveFactoryPathFile(IJavaProject jproj, Map<FactoryContainer, FactoryPath.Attributes> containers)
throws CoreException
{
IFile projFile;
File wkspFile;
if (jproj != null) {
projFile = getIFileForProject(jproj);
wkspFile = null;
}
else {
wkspFile = getFileForWorkspace();
projFile = null;
}
try {
if (containers != null) {
String data = FactoryPathUtil.encodeFactoryPath(containers);
// If project is null, set workspace-level data
if (jproj == null) {
FileSystemUtil.writeStringToFile(wkspFile, data);
}
else {
FileSystemUtil.writeStringToIFile(projFile, data);
}
}
else { // restore defaults by deleting the factorypath file.
if (jproj != null) {
projFile.delete(true, null);
}
else {
wkspFile.delete();
}
}
}
catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, AptPlugin.PLUGIN_ID, -1, Messages.FactoryPathUtil_status_ioException, e));
}
}
/**
* Returns an XML string encoding all of the factories.
* @param factories
*/
public static String encodeFactoryPath(Map<FactoryContainer, FactoryPath.Attributes> factories) {
StringBuilder sb = new StringBuilder();
sb.append("<").append(FACTORYPATH_TAG).append(">\n"); //$NON-NLS-1$ //$NON-NLS-2$
for (Map.Entry<FactoryContainer, FactoryPath.Attributes> entry : factories.entrySet()) {
FactoryContainer container = entry.getKey();
FactoryPath.Attributes attr = entry.getValue();
sb.append(INDENT);
sb.append("<"); //$NON-NLS-1$
sb.append(FACTORYPATH_ENTRY_TAG).append(" "); //$NON-NLS-1$
sb.append(KIND).append("=\"").append(container.getType()).append("\" "); //$NON-NLS-1$ //$NON-NLS-2$
sb.append(ID).append("=\"").append(container.getId()).append("\" "); //$NON-NLS-1$ //$NON-NLS-2$
sb.append(ENABLED).append("=\"").append(attr.isEnabled()).append("\" "); //$NON-NLS-1$ //$NON-NLS-2$
sb.append(RUN_IN_BATCH_MODE).append("=\"").append(attr.runInBatchMode()).append("\"/>\n"); //$NON-NLS-1$ //$NON-NLS-2$
}
sb.append("</").append(FACTORYPATH_TAG).append(">\n"); //$NON-NLS-1$ //$NON-NLS-2$
return sb.toString();
}
/**
* Create a factory container based on an external jar file (not in the
* workspace).
* @param jar a java.io.File representing the jar file.
*/
public static FactoryContainer newExtJarFactoryContainer(File jar) {
return new ExtJarFactoryContainer(jar);
}
/**
* Create a factory container based on a jar file in the workspace.
* @param jar an Eclipse IPath representing the jar file; the path is
* relative to the workspace root.
*/
public static FactoryContainer newWkspJarFactoryContainer(IPath jar) {
return new WkspJarFactoryContainer(jar);
}
/**
* Create a factory container based on an external jar file specified
* by a classpath variable (and possibly a path relative to that variable).
* @param jar an Eclipse IPath representing the jar file; the first
* segment of the path is assumed to be the variable name.
*/
public static FactoryContainer newVarJarFactoryContainer(IPath jar) {
return new VarJarFactoryContainer(jar);
}
public static Map<FactoryContainer, FactoryPath.Attributes> decodeFactoryPath(final String xmlFactoryPath)
throws CoreException
{
Map<FactoryContainer, FactoryPath.Attributes> result = new LinkedHashMap<FactoryContainer, FactoryPath.Attributes>();
StringReader reader = new StringReader(xmlFactoryPath);
Element fpElement = null;
try {
DocumentBuilder parser =
DocumentBuilderFactory.newInstance().newDocumentBuilder();
fpElement = parser.parse(new InputSource(reader)).getDocumentElement();
}
catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, AptPlugin.PLUGIN_ID, -1, Messages.FactoryPathUtil_status_ioException, e));
}
catch (SAXException e) {
throw new CoreException(new Status(IStatus.ERROR, AptPlugin.PLUGIN_ID, -1, Messages.FactoryPathUtil_status_couldNotParse, e));
}
catch (ParserConfigurationException e) {
throw new CoreException(new Status(IStatus.ERROR, AptPlugin.PLUGIN_ID, -1, Messages.FactoryPathUtil_status_parserConfigError, e));
}
finally {
reader.close();
}
if (!fpElement.getNodeName().equalsIgnoreCase(FACTORYPATH_TAG)) {
IOException e = new IOException("Incorrect file format. File must begin with " + FACTORYPATH_TAG); //$NON-NLS-1$
throw new CoreException(new Status(IStatus.ERROR, AptPlugin.PLUGIN_ID, -1, Messages.FactoryPathUtil_status_ioException, e));
}
NodeList nodes = fpElement.getElementsByTagName(FACTORYPATH_ENTRY_TAG);
for (int i=0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element element = (Element)node;
String kindString = element.getAttribute(KIND);
// deprecated container type "JAR" is now "EXTJAR"
if ("JAR".equals(kindString)) { //$NON-NLS-1$
kindString = "EXTJAR"; //$NON-NLS-1$
}
String idString = element.getAttribute(ID);
String enabledString = element.getAttribute(ENABLED);
String runInAptModeString = element.getAttribute(RUN_IN_BATCH_MODE);
FactoryType kind = FactoryType.valueOf(kindString);
FactoryContainer container = null;
switch (kind) {
case WKSPJAR :
container = newWkspJarFactoryContainer(new Path(idString));
break;
case EXTJAR :
container = newExtJarFactoryContainer(new File(idString));
break;
case VARJAR :
container = newVarJarFactoryContainer(new Path(idString));
break;
case PLUGIN :
container = FactoryPluginManager.getPluginFactoryContainer(idString);
break;
default :
throw new IllegalStateException("Unrecognized kind: " + kind + ". Original string: " + kindString); //$NON-NLS-1$ //$NON-NLS-2$
}
if (null != container) {
FactoryPath.Attributes a = new FactoryPath.Attributes(
Boolean.parseBoolean(enabledString), Boolean.parseBoolean(runInAptModeString));
result.put(container, a);
}
}
}
return result;
}
/**
* Get a file designator for the workspace-level factory path settings file.
* Typically this is [workspace]/.metadata/plugins/org.eclipse.jdt.apt.core/.factorypath
* @return a java.io.File
*/
private static File getFileForWorkspace() {
return AptPlugin.getPlugin().getStateLocation().append(FACTORYPATH_FILE).toFile();
}
/**
* Get an Eclipse IFile for the project-level factory path settings file.
* Typically this is [project]/.factorypath
* @param jproj must not be null
* @return an Eclipse IFile
*/
private static IFile getIFileForProject(IJavaProject jproj) {
IProject proj = jproj.getProject();
return proj.getFile(FACTORYPATH_FILE);
}
/**
* Does a factory path file already exist for the specified project,
* or for the workspace as a whole?
* @param jproj if this is null, check for workspace-level settings.
* @return true if a settings file exists.
*/
public static boolean doesFactoryPathFileExist(IJavaProject jproj) {
if (jproj == null) {
File wkspFile = getFileForWorkspace();
return wkspFile.exists();
}
else {
IFile projFile = getIFileForProject(jproj);
return projFile.exists();
}
}
/**
* Calculates the active factory path for the specified project. This
* depends on the stored information in the .factorypath file, as well as
* on the set of plugins that were found at load time of this Eclipse instance.
* Returns all containers for the provided project, including disabled ones.
* @param jproj The java project in question, or null for the workspace
* @return an ordered map, where the key is the container and the value
* indicates whether the container is enabled.
*/
private static synchronized Map<FactoryContainer, FactoryPath.Attributes> calculatePath(IJavaProject jproj) {
Map<FactoryContainer, FactoryPath.Attributes> map = null;
boolean foundPerProjFile = false;
if (jproj != null) {
try {
map = FactoryPathUtil.readFactoryPathFile(jproj);
foundPerProjFile = (map != null);
}
catch (CoreException ce) {
AptPlugin.log(ce, "Could not get factory containers for project: " + jproj); //$NON-NLS-1$
}
}
// Workspace if no project data was found
if (map == null) {
try {
map = FactoryPathUtil.readFactoryPathFile(null);
}
catch (CoreException ce) {
AptPlugin.log(ce, "Could not get factory containers for project: " + jproj); //$NON-NLS-1$
}
}
// if no project and no workspace data was found, we'll get the defaults
if (map == null) {
map = new LinkedHashMap<FactoryContainer, FactoryPath.Attributes>();
}
boolean disableNewPlugins = (jproj != null) && foundPerProjFile;
updatePluginContainers(map, disableNewPlugins);
return map;
}
/**
* Removes missing plugin containers, and adds any plugin containers
* that were added since the map was originally created. The order
* of the original list will be maintained, and new entries will be
* added to the end of the list in alphabetic order. The resulting
* list has the same contents as PLUGIN_FACTORY_MAP (that is, all the
* loaded plugins and nothing else), but the order is as close as possible
* to the input.
*
* @param path the factory path (in raw Map form) to be modified.
* @param disableNewPlugins if true, newly discovered plugins will be
* disabled. If false, they will be enabled or disabled according to
* their setting in the extension declaration.
*/
private static void updatePluginContainers(
Map<FactoryContainer, FactoryPath.Attributes> path, boolean disableNewPlugins) {
// Get the alphabetically-ordered list of all plugins we found at startup.
Map<FactoryContainer, FactoryPath.Attributes> pluginContainers = FactoryPluginManager.getAllPluginFactoryContainers();
// Remove from the path any plugins which we did not find at startup
for (Iterator<FactoryContainer> i = path.keySet().iterator(); i.hasNext(); ) {
FactoryContainer fc = i.next();
if (fc.getType() == FactoryContainer.FactoryType.PLUGIN && !pluginContainers.containsKey(fc)) {
i.remove();
}
}
// Add to the end any plugins which are not in the path (i.e., which
// have been discovered since the config was last saved)
for (Map.Entry<FactoryContainer, FactoryPath.Attributes> entry : pluginContainers.entrySet()) {
if (!path.containsKey(entry.getKey())) {
FactoryPath.Attributes newAttr;
FactoryPath.Attributes oldAttr = entry.getValue();
if (disableNewPlugins) {
newAttr = new FactoryPath.Attributes(false, oldAttr.runInBatchMode());
} else {
newAttr = oldAttr;
}
path.put(entry.getKey(), newAttr);
}
}
}
/**
* Get a factory path corresponding to the default values: if jproj is
* non-null, return the current workspace factory path (workspace prefs
* are the default for a project); if jproj is null, return the default
* list of plugin factories (which is the "factory default").
*/
public static IFactoryPath getDefaultFactoryPath(IJavaProject jproj) {
FactoryPath fp = new FactoryPath();
if (jproj != null) {
fp.setContainers(calculatePath(null));
}
else {
fp.setContainers(FactoryPluginManager.getAllPluginFactoryContainers());
}
return fp;
}
public static FactoryPath getFactoryPath(IJavaProject jproj) {
Map<FactoryContainer, FactoryPath.Attributes> map = calculatePath(jproj);
FactoryPath fp = new FactoryPath();
fp.setContainers(map);
return fp;
}
public static void setFactoryPath(IJavaProject jproj, FactoryPath path)
throws CoreException {
Map<FactoryContainer, FactoryPath.Attributes> map = (path != null) ?
path.getAllContainers() : null;
saveFactoryPathFile(jproj, map);
}
}