/******************************************************************************* * Copyright (c) 2005, 2015 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<>(); 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<>(); } 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); } }