/******************************************************************************* * Copyright (c) 2012 Pivotal Software, 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: * Pivotal Software, Inc. - initial API and implementation *******************************************************************************/ package org.grails.ide.eclipse.core.internal.classpath; import java.io.File; import java.util.HashMap; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.grails.ide.eclipse.core.GrailsCoreActivator; import org.grails.ide.eclipse.core.internal.plugins.GrailsCore; import org.grails.ide.eclipse.core.internal.plugins.GrailsElementKind; import org.grails.ide.eclipse.core.internal.plugins.IGrailsProjectInfo; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; /** * Caches association between dependencies in classpath container and their sources and javadocs. * @author Kris De Volder * * @since 2.8 */ public class PerProjectAttachementsCache implements IGrailsProjectInfo { /** * Determines key used to find attachements associated with a jar. We need to use * this because the jar themselves aren't unique (the ones from buildsettings are in different * locations than the one returned by the Grails 2.0 refresh-dependencies command. */ public static String getJarKey(String jar) { Path jarPath = new Path(jar); return jarPath.lastSegment(); } /** * A parsed xml dependency element from the xml file produced by grails refresh dependencies. * * @author Kris De Volder * * @since 2.8 */ public static class Dependency { private String key; private String sources; private String javadoc; public String getSources() { return sources; } public String getJavadoc() { return javadoc; } public Dependency(String jar, String sources, String javadoc) { this.key = getJarKey(jar); this.sources = sources; this.javadoc = javadoc; } /** * True the element contains no real data (i.e. no source or javadoc associated with the jar. */ public boolean isEmpty() { return key==null || sources==null && javadoc==null; } public String getKey() { return key; } } private IProject project; private Map<String, Dependency> data; public IProject getProject() { return project; } public void setProject(IProject project) { this.project = project; } public void projectChanged(GrailsElementKind[] changeKinds, IResourceDelta change) { // don't care } public void dispose() { synchronized (GrailsCore.get().getLockForProject(project)) { data = null; //purge the data // But do not purge the data file. See: https://issuetracker.springsource.com/browse/STS-2538 // When refresh dependencies is called all caches for that project will be disposed. See // org.grails.ide.eclipse.core.internal.classpath.GrailsClasspathContainerUpdateJob.runInWorkspace(IProgressMonitor) // and issue STS-2247 // String fileName = GrailsClasspathUtils.getDependencySourcesDescriptorName(project); // if (fileName!=null) { // //filename can be null when eclipse is shutting down, because we can't determine plugin state location. // File dataFile = new File(fileName); // if (dataFile.exists()) { // dataFile.delete(); // } // } } } public void refreshData() { data = null; } public Dependency getAttachments(String dependency) { synchronized (GrailsCore.get().getLockForProject(project)) { if (data == null) { data = parseData(new File(GrailsClasspathUtils.getDependencySourcesDescriptorName(project))); } if (data!=null) { return data.get(dependency); } return null; } } public IPath getSourceAttachement(String dependency) { String key = getJarKey(dependency); Dependency att = getAttachments(key); if (att!=null) { return new Path(att.getSources()); } return null; } /** * This method parses attachement data from file. The method is only plublic to allow it to be used in unit testing. * In normal operation the data should only be accessed via the cache. */ public static Map<String, Dependency> parseData(File file) { Map<String, Dependency> result = new HashMap<String, Dependency>(); try { if (file!=null && file.exists()) { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); Document doc = docBuilder.parse(file); NodeList allDeps = doc.getElementsByTagName("dependency"); for (int i = 0; i < allDeps.getLength(); i++) { Node dep = allDeps.item(i); Dependency parsed = parseDependency(dep); if (parsed!=null) { result.put(parsed.getKey(), parsed); } } return result; } } catch (Exception e) { GrailsCoreActivator.log(e); } if (!result.isEmpty()) { return result; // in case of errors may still have partial data! } return null; } private static Dependency parseDependency(Node dep) { if (dep.getNodeType()==Node.ELEMENT_NODE) { Element el = (Element) dep; if (el.getNodeName().equals("dependency")) { Dependency parsed = new Dependency( DomUtils.getChildElementValueByTagName(el, "jar"), DomUtils.getChildElementValueByTagName(el, "source"), DomUtils.getChildElementValueByTagName(el, "javadoc")); if (!parsed.isEmpty()) { return parsed; } } } return null; } public static PerProjectAttachementsCache get(IProject project) { PerProjectAttachementsCache cache = GrailsCore.get().connect(project, PerProjectAttachementsCache.class); return cache; } }