001: /*******************************************************************************
002: * Copyright (c) 2005, 2007 BEA Systems, Inc.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * jgarms@bea.com - initial API and implementation
010: *
011: *******************************************************************************/package org.eclipse.jdt.apt.core.internal.util;
012:
013: import java.io.File;
014: import java.io.IOException;
015: import java.io.StringReader;
016: import java.util.Iterator;
017: import java.util.LinkedHashMap;
018: import java.util.Map;
019:
020: import javax.xml.parsers.DocumentBuilder;
021: import javax.xml.parsers.DocumentBuilderFactory;
022: import javax.xml.parsers.ParserConfigurationException;
023:
024: import org.eclipse.core.resources.IFile;
025: import org.eclipse.core.resources.IProject;
026: import org.eclipse.core.resources.IResource;
027: import org.eclipse.core.runtime.CoreException;
028: import org.eclipse.core.runtime.IPath;
029: import org.eclipse.core.runtime.IStatus;
030: import org.eclipse.core.runtime.Path;
031: import org.eclipse.core.runtime.Status;
032: import org.eclipse.jdt.apt.core.internal.AptPlugin;
033: import org.eclipse.jdt.apt.core.internal.ExtJarFactoryContainer;
034: import org.eclipse.jdt.apt.core.internal.FactoryPluginManager;
035: import org.eclipse.jdt.apt.core.internal.VarJarFactoryContainer;
036: import org.eclipse.jdt.apt.core.internal.WkspJarFactoryContainer;
037: import org.eclipse.jdt.apt.core.internal.util.FactoryContainer.FactoryType;
038: import org.eclipse.jdt.apt.core.util.IFactoryPath;
039: import org.eclipse.jdt.core.IJavaProject;
040: import org.w3c.dom.Element;
041: import org.w3c.dom.Node;
042: import org.w3c.dom.NodeList;
043: import org.xml.sax.InputSource;
044: import org.xml.sax.SAXException;
045:
046: /**
047: * Utility class for dealing with the factory path
048: */
049: public final class FactoryPathUtil {
050:
051: private static final String FACTORYPATH_TAG = "factorypath"; //$NON-NLS-1$
052: private static final String FACTORYPATH_ENTRY_TAG = "factorypathentry"; //$NON-NLS-1$
053: private static final String KIND = "kind"; //$NON-NLS-1$
054: private static final String ID = "id"; //$NON-NLS-1$
055: private static final String ENABLED = "enabled"; //$NON-NLS-1$
056: private static final String RUN_IN_BATCH_MODE = "runInBatchMode"; //$NON-NLS-1$
057:
058: private static final String FACTORYPATH_FILE = ".factorypath"; //$NON-NLS-1$
059:
060: // four spaces for indent
061: private static final String INDENT = " "; //$NON-NLS-1$
062:
063: // Private c-tor to prevent construction
064: private FactoryPathUtil() {
065: }
066:
067: /**
068: * Test whether a resource is a factory path file. The criteria are
069: * that it is a file, it belongs to a project, it is located in the root
070: * of that project, and it is named ".factorypath". Note that the
071: * workspace-wide factorypath file does NOT meet these criteria.
072: * @param res any sort of IResource, or null.
073: * @return true if the resource is a project-specific factory path file.
074: */
075: public static boolean isFactoryPathFile(IResource res) {
076: if (res == null || res.getType() != IResource.FILE
077: || res.getProject() == null) {
078: return false;
079: }
080: IPath path = res.getProjectRelativePath();
081: if (path.segmentCount() != 1) {
082: return false;
083: }
084: return FACTORYPATH_FILE.equals(path.lastSegment());
085: }
086:
087: /**
088: * Loads a map of factory containers from the factory path for a given
089: * project. If no factorypath file was found, returns null.
090: */
091: public static Map<FactoryContainer, FactoryPath.Attributes> readFactoryPathFile(
092: IJavaProject jproj) throws CoreException {
093: String data = null;
094: try {
095: // If project is null, use workspace-level data
096: if (jproj == null) {
097: File file = getFileForWorkspace();
098: if (!file.exists()) {
099: return null;
100: }
101: data = FileSystemUtil.getContentsOfFile(file);
102: } else {
103: IFile ifile = getIFileForProject(jproj);
104: if (!ifile.exists()) {
105: return null;
106: }
107: data = FileSystemUtil.getContentsOfIFile(ifile);
108: }
109: } catch (IOException e) {
110: throw new CoreException(new Status(IStatus.ERROR,
111: AptPlugin.PLUGIN_ID, -1,
112: Messages.FactoryPathUtil_status_ioException, e));
113: }
114:
115: return FactoryPathUtil.decodeFactoryPath(data);
116: }
117:
118: /**
119: * Stores a map of factory containers to the factorypath file
120: * for a given project. If null is passed in, the factorypath file
121: * is deleted.
122: */
123: public static void saveFactoryPathFile(IJavaProject jproj,
124: Map<FactoryContainer, FactoryPath.Attributes> containers)
125: throws CoreException {
126: IFile projFile;
127: File wkspFile;
128: if (jproj != null) {
129: projFile = getIFileForProject(jproj);
130: wkspFile = null;
131: } else {
132: wkspFile = getFileForWorkspace();
133: projFile = null;
134: }
135:
136: try {
137: if (containers != null) {
138: String data = FactoryPathUtil
139: .encodeFactoryPath(containers);
140: // If project is null, set workspace-level data
141: if (jproj == null) {
142: FileSystemUtil.writeStringToFile(wkspFile, data);
143: } else {
144: FileSystemUtil.writeStringToIFile(projFile, data);
145: }
146: } else { // restore defaults by deleting the factorypath file.
147: if (jproj != null) {
148: projFile.delete(true, null);
149: } else {
150: wkspFile.delete();
151: }
152: }
153: } catch (IOException e) {
154: throw new CoreException(new Status(IStatus.ERROR,
155: AptPlugin.PLUGIN_ID, -1,
156: Messages.FactoryPathUtil_status_ioException, e));
157: }
158: }
159:
160: /**
161: * Returns an XML string encoding all of the factories.
162: * @param factories
163: */
164: public static String encodeFactoryPath(
165: Map<FactoryContainer, FactoryPath.Attributes> factories) {
166: StringBuilder sb = new StringBuilder();
167: sb.append("<").append(FACTORYPATH_TAG).append(">\n"); //$NON-NLS-1$ //$NON-NLS-2$
168: for (Map.Entry<FactoryContainer, FactoryPath.Attributes> entry : factories
169: .entrySet()) {
170: FactoryContainer container = entry.getKey();
171: FactoryPath.Attributes attr = entry.getValue();
172: sb.append(INDENT);
173: sb.append("<"); //$NON-NLS-1$
174: sb.append(FACTORYPATH_ENTRY_TAG).append(" "); //$NON-NLS-1$
175: sb
176: .append(KIND)
177: .append("=\"").append(container.getType()).append("\" "); //$NON-NLS-1$ //$NON-NLS-2$
178: sb
179: .append(ID)
180: .append("=\"").append(container.getId()).append("\" "); //$NON-NLS-1$ //$NON-NLS-2$
181: sb
182: .append(ENABLED)
183: .append("=\"").append(attr.isEnabled()).append("\" "); //$NON-NLS-1$ //$NON-NLS-2$
184: sb
185: .append(RUN_IN_BATCH_MODE)
186: .append("=\"").append(attr.runInBatchMode()).append("\"/>\n"); //$NON-NLS-1$ //$NON-NLS-2$
187: }
188: sb.append("</").append(FACTORYPATH_TAG).append(">\n"); //$NON-NLS-1$ //$NON-NLS-2$
189:
190: return sb.toString();
191: }
192:
193: /**
194: * Create a factory container based on an external jar file (not in the
195: * workspace).
196: * @param jar a java.io.File representing the jar file.
197: */
198: public static FactoryContainer newExtJarFactoryContainer(File jar) {
199: return new ExtJarFactoryContainer(jar);
200: }
201:
202: /**
203: * Create a factory container based on a jar file in the workspace.
204: * @param jar an Eclipse IPath representing the jar file; the path is
205: * relative to the workspace root.
206: */
207: public static FactoryContainer newWkspJarFactoryContainer(IPath jar) {
208: return new WkspJarFactoryContainer(jar);
209: }
210:
211: /**
212: * Create a factory container based on an external jar file specified
213: * by a classpath variable (and possibly a path relative to that variable).
214: * @param jar an Eclipse IPath representing the jar file; the first
215: * segment of the path is assumed to be the variable name.
216: */
217: public static FactoryContainer newVarJarFactoryContainer(IPath jar) {
218: return new VarJarFactoryContainer(jar);
219: }
220:
221: public static Map<FactoryContainer, FactoryPath.Attributes> decodeFactoryPath(
222: final String xmlFactoryPath) throws CoreException {
223: Map<FactoryContainer, FactoryPath.Attributes> result = new LinkedHashMap<FactoryContainer, FactoryPath.Attributes>();
224: StringReader reader = new StringReader(xmlFactoryPath);
225: Element fpElement = null;
226:
227: try {
228: DocumentBuilder parser = DocumentBuilderFactory
229: .newInstance().newDocumentBuilder();
230: fpElement = parser.parse(new InputSource(reader))
231: .getDocumentElement();
232:
233: } catch (IOException e) {
234: throw new CoreException(new Status(IStatus.ERROR,
235: AptPlugin.PLUGIN_ID, -1,
236: Messages.FactoryPathUtil_status_ioException, e));
237: } catch (SAXException e) {
238: throw new CoreException(new Status(IStatus.ERROR,
239: AptPlugin.PLUGIN_ID, -1,
240: Messages.FactoryPathUtil_status_couldNotParse, e));
241: } catch (ParserConfigurationException e) {
242: throw new CoreException(new Status(IStatus.ERROR,
243: AptPlugin.PLUGIN_ID, -1,
244: Messages.FactoryPathUtil_status_parserConfigError,
245: e));
246: } finally {
247: reader.close();
248: }
249:
250: if (!fpElement.getNodeName().equalsIgnoreCase(FACTORYPATH_TAG)) {
251: IOException e = new IOException(
252: "Incorrect file format. File must begin with " + FACTORYPATH_TAG); //$NON-NLS-1$
253: throw new CoreException(new Status(IStatus.ERROR,
254: AptPlugin.PLUGIN_ID, -1,
255: Messages.FactoryPathUtil_status_ioException, e));
256: }
257: NodeList nodes = fpElement
258: .getElementsByTagName(FACTORYPATH_ENTRY_TAG);
259: for (int i = 0; i < nodes.getLength(); i++) {
260: Node node = nodes.item(i);
261: if (node.getNodeType() == Node.ELEMENT_NODE) {
262: Element element = (Element) node;
263: String kindString = element.getAttribute(KIND);
264: // deprecated container type "JAR" is now "EXTJAR"
265: if ("JAR".equals(kindString)) { //$NON-NLS-1$
266: kindString = "EXTJAR"; //$NON-NLS-1$
267: }
268: String idString = element.getAttribute(ID);
269: String enabledString = element.getAttribute(ENABLED);
270: String runInAptModeString = element
271: .getAttribute(RUN_IN_BATCH_MODE);
272: FactoryType kind = FactoryType.valueOf(kindString);
273: FactoryContainer container = null;
274: switch (kind) {
275:
276: case WKSPJAR:
277: container = newWkspJarFactoryContainer(new Path(
278: idString));
279: break;
280:
281: case EXTJAR:
282: container = newExtJarFactoryContainer(new File(
283: idString));
284: break;
285:
286: case VARJAR:
287: container = newVarJarFactoryContainer(new Path(
288: idString));
289: break;
290:
291: case PLUGIN:
292: container = FactoryPluginManager
293: .getPluginFactoryContainer(idString);
294: break;
295:
296: default:
297: throw new IllegalStateException(
298: "Unrecognized kind: " + kind + ". Original string: " + kindString); //$NON-NLS-1$ //$NON-NLS-2$
299: }
300:
301: if (null != container) {
302: FactoryPath.Attributes a = new FactoryPath.Attributes(
303: Boolean.parseBoolean(enabledString),
304: Boolean.parseBoolean(runInAptModeString));
305: result.put(container, a);
306: }
307: }
308: }
309:
310: return result;
311: }
312:
313: /**
314: * Get a file designator for the workspace-level factory path settings file.
315: * Typically this is [workspace]/.metadata/plugins/org.eclipse.jdt.apt.core/.factorypath
316: * @return a java.io.File
317: */
318: private static File getFileForWorkspace() {
319: return AptPlugin.getPlugin().getStateLocation().append(
320: FACTORYPATH_FILE).toFile();
321: }
322:
323: /**
324: * Get an Eclipse IFile for the project-level factory path settings file.
325: * Typically this is [project]/.factorypath
326: * @param jproj must not be null
327: * @return an Eclipse IFile
328: */
329: private static IFile getIFileForProject(IJavaProject jproj) {
330: IProject proj = jproj.getProject();
331: return proj.getFile(FACTORYPATH_FILE);
332: }
333:
334: /**
335: * Does a factory path file already exist for the specified project,
336: * or for the workspace as a whole?
337: * @param jproj if this is null, check for workspace-level settings.
338: * @return true if a settings file exists.
339: */
340: public static boolean doesFactoryPathFileExist(IJavaProject jproj) {
341: if (jproj == null) {
342: File wkspFile = getFileForWorkspace();
343: return wkspFile.exists();
344: } else {
345: IFile projFile = getIFileForProject(jproj);
346: return projFile.exists();
347: }
348: }
349:
350: /**
351: * Calculates the active factory path for the specified project. This
352: * depends on the stored information in the .factorypath file, as well as
353: * on the set of plugins that were found at load time of this Eclipse instance.
354: * Returns all containers for the provided project, including disabled ones.
355: * @param jproj The java project in question, or null for the workspace
356: * @return an ordered map, where the key is the container and the value
357: * indicates whether the container is enabled.
358: */
359: private static synchronized Map<FactoryContainer, FactoryPath.Attributes> calculatePath(
360: IJavaProject jproj) {
361: Map<FactoryContainer, FactoryPath.Attributes> map = null;
362: boolean foundPerProjFile = false;
363: if (jproj != null) {
364: try {
365: map = FactoryPathUtil.readFactoryPathFile(jproj);
366: foundPerProjFile = (map != null);
367: } catch (CoreException ce) {
368: AptPlugin
369: .log(
370: ce,
371: "Could not get factory containers for project: " + jproj); //$NON-NLS-1$
372: }
373: }
374: // Workspace if no project data was found
375: if (map == null) {
376: try {
377: map = FactoryPathUtil.readFactoryPathFile(null);
378: } catch (CoreException ce) {
379: AptPlugin
380: .log(
381: ce,
382: "Could not get factory containers for project: " + jproj); //$NON-NLS-1$
383: }
384: }
385: // if no project and no workspace data was found, we'll get the defaults
386: if (map == null) {
387: map = new LinkedHashMap<FactoryContainer, FactoryPath.Attributes>();
388: }
389: boolean disableNewPlugins = (jproj != null) && foundPerProjFile;
390: updatePluginContainers(map, disableNewPlugins);
391: return map;
392: }
393:
394: /**
395: * Removes missing plugin containers, and adds any plugin containers
396: * that were added since the map was originally created. The order
397: * of the original list will be maintained, and new entries will be
398: * added to the end of the list in alphabetic order. The resulting
399: * list has the same contents as PLUGIN_FACTORY_MAP (that is, all the
400: * loaded plugins and nothing else), but the order is as close as possible
401: * to the input.
402: *
403: * @param path the factory path (in raw Map form) to be modified.
404: * @param disableNewPlugins if true, newly discovered plugins will be
405: * disabled. If false, they will be enabled or disabled according to
406: * their setting in the extension declaration.
407: */
408: private static void updatePluginContainers(
409: Map<FactoryContainer, FactoryPath.Attributes> path,
410: boolean disableNewPlugins) {
411:
412: // Get the alphabetically-ordered list of all plugins we found at startup.
413: Map<FactoryContainer, FactoryPath.Attributes> pluginContainers = FactoryPluginManager
414: .getAllPluginFactoryContainers();
415:
416: // Remove from the path any plugins which we did not find at startup
417: for (Iterator<FactoryContainer> i = path.keySet().iterator(); i
418: .hasNext();) {
419: FactoryContainer fc = i.next();
420: if (fc.getType() == FactoryContainer.FactoryType.PLUGIN
421: && !pluginContainers.containsKey(fc)) {
422: i.remove();
423: }
424: }
425:
426: // Add to the end any plugins which are not in the path (i.e., which
427: // have been discovered since the config was last saved)
428: for (Map.Entry<FactoryContainer, FactoryPath.Attributes> entry : pluginContainers
429: .entrySet()) {
430: if (!path.containsKey(entry.getKey())) {
431: FactoryPath.Attributes newAttr;
432: FactoryPath.Attributes oldAttr = entry.getValue();
433: if (disableNewPlugins) {
434: newAttr = new FactoryPath.Attributes(false, oldAttr
435: .runInBatchMode());
436: } else {
437: newAttr = oldAttr;
438: }
439: path.put(entry.getKey(), newAttr);
440: }
441: }
442: }
443:
444: /**
445: * Get a factory path corresponding to the default values: if jproj is
446: * non-null, return the current workspace factory path (workspace prefs
447: * are the default for a project); if jproj is null, return the default
448: * list of plugin factories (which is the "factory default").
449: */
450: public static IFactoryPath getDefaultFactoryPath(IJavaProject jproj) {
451: FactoryPath fp = new FactoryPath();
452: if (jproj != null) {
453: fp.setContainers(calculatePath(null));
454: } else {
455: fp.setContainers(FactoryPluginManager
456: .getAllPluginFactoryContainers());
457: }
458: return fp;
459: }
460:
461: public static FactoryPath getFactoryPath(IJavaProject jproj) {
462: Map<FactoryContainer, FactoryPath.Attributes> map = calculatePath(jproj);
463: FactoryPath fp = new FactoryPath();
464: fp.setContainers(map);
465: return fp;
466: }
467:
468: public static void setFactoryPath(IJavaProject jproj,
469: FactoryPath path) throws CoreException {
470: Map<FactoryContainer, FactoryPath.Attributes> map = (path != null) ? path
471: .getAllContainers()
472: : null;
473: saveFactoryPathFile(jproj, map);
474: }
475:
476: }
|