/** * Copyright (C) 2005 - 2013 Eric Van Dewoestine * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.eclim.plugin.core.project; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Set; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPathExpression; import org.apache.commons.lang.StringUtils; import org.eclim.command.CommandLine; import org.eclim.command.Error; import org.eclim.command.Options; import org.eclim.logging.Logger; import org.eclim.plugin.core.preference.Preferences; import org.eclim.plugin.core.project.ProjectNatureFactory; import org.eclim.plugin.core.util.ProjectUtils; import org.eclim.plugin.core.util.XmlUtils; import org.eclim.util.CollectionUtils; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.w3c.dom.Document; /** * Class that handles registering and retrieving of {@link ProjectManager}s. * * @author Eric Van Dewoestine */ public class ProjectManagement { private static final Logger logger = Logger.getLogger(ProjectManagement.class); private static HashMap<String, ProjectManager> managers = new HashMap<String, ProjectManager>(); private static XPathExpression xpath; private static DocumentBuilderFactory factory; /** * Registers a ProjectManager. * * @param nature The project nature that the manager will manage. * @param manager The ProjectManager. * @return The ProjectManager. */ public static ProjectManager addProjectManager( String nature, ProjectManager manager) { logger.debug("add project manager: nature: {} manager: {}", nature, manager); managers.put(nature, manager); return manager; } /** * Gets a ProjectManager. * * @param nature The nature to get the ProjectManager for. * @return The ProjectManager or null if none. */ public static ProjectManager getProjectManager(String nature) { return managers.get(nature); } /** * Gets an array of all register project manager nature names. * * @return Array of nature names. */ public static String[] getProjectManagerNatures() { Set<String> registered = managers.keySet(); return registered.toArray(new String[registered.size()]); } /** * Gets an array of all register project managers. * * @return Array of ProjectManager. */ public static ProjectManager[] getProjectManagers() { Collection<ProjectManager> registered = managers.values(); return registered.toArray(new ProjectManager[registered.size()]); } /** * Creates a project. * * @param name The project name to use. * @param folder The folder to create the project at. * @param commandLine The command line for the project create command. */ public static void create( String name, String folder, CommandLine commandLine) throws Exception { String[] aliases = StringUtils.split( commandLine.getValue(Options.NATURE_OPTION), ','); // convert from aliases to real nature names. ArrayList<String> natures = new ArrayList<String>(); for (int ii = 0; ii < aliases.length; ii++){ if(!ProjectNatureFactory.NONE.equals(aliases[ii])){ String[] ids = ProjectNatureFactory.getNaturesForAlias(aliases[ii]); if (ids != null){ for (String id : ids){ natures.add(id); } }else{ String[] registered = ProjectNatureFactory.getNatureAliases(); StringBuffer supported = new StringBuffer(); for (String key : registered){ if(supported.length() > 0){ supported.append(", "); } supported.append(key).append('=') .append(ProjectNatureFactory.getNatureForAlias(key)); } throw new IllegalArgumentException( "Unable to find nature for alias '" + aliases[ii] + "'. " + "Supported aliases include: " + supported); } } } deleteStaleProject(name, folder); IProject project = createProject( name, folder, (String[])natures.toArray(new String[natures.size()])); project.open(null); // calling refresh for those project created against an existing code base. // performing a preemptive refresh prevents ProjectUtils.getFile // (IFile.refreshLocal) from kicking off a rebuild workspace job, which in // turn can cause issues with pdt select and completion engines // (See PhpUtils.waitOnBuild). refresh(project, commandLine, false); for (int ii = 0; ii < natures.size(); ii++){ ProjectManager manager = getProjectManager((String)natures.get(ii)); if(manager != null){ manager.create(project, commandLine); } } Preferences.getInstance().clearProjectValueCache(project); } /** * Handle creation of project if necessary. * * @param name The project name. * @param folder The project folder. * @param natures Array of natures. * * @return The created project. */ protected static IProject createProject( String name, String folder, String[] natures) throws Exception { // create the project if it doesn't already exist. IProject project = ProjectUtils.getProject(name, true); if(!project.exists()){ IWorkspace workspace = ResourcesPlugin.getWorkspace(); IPath location = new Path(folder); // location must not overlap the workspace (unless it's a nested project) IPath workspaceLocation = workspace.getRoot().getRawLocation(); IPath relLocation = null; if (location.toOSString().toLowerCase().startsWith( workspaceLocation.toOSString().toLowerCase())) { relLocation = location.removeFirstSegments( location.matchingFirstSegments(workspaceLocation)); } // project is outside the workspace otherwise not at the top of the // workspace if (relLocation == null || relLocation.segmentCount() != 1) { IProjectDescription description = workspace.newProjectDescription(name); description.setLocation(location); description.setNatureIds(natures); project.create(description, null/*monitor*/); // project is at the workspace root, requires jumping through eclipse // hoops }else{ String relName = relLocation.toString(); // hack for windows... manually remove drive letter relName = relName.replaceFirst("^[a-zA-Z]:", ""); project = ProjectUtils.getProject(relName, true); if(!project.exists()){ IProjectDescription description = workspace.newProjectDescription(relName); description.setNatureIds(natures); project.create(description, null/*monitor*/); // FIXME: eclipse will ignore this name change. need to find the // proper way to rename a project in the workspace root if we want to // support this. /*project.open(null); description = project.getDescription(); description.setName(name);*/ } } // project exists, so just add any missing requested natures }else{ IProjectDescription desc = project.getDescription(); String[] natureIds = desc.getNatureIds(); ArrayList<String> modified = new ArrayList<String>(); ArrayList<String> newNatures = new ArrayList<String>(); CollectionUtils.addAll(modified, natureIds); for(String natureId : natures){ if (!modified.contains(natureId)){ modified.add(natureId); newNatures.add(natureId); } } desc.setNatureIds((String[])modified.toArray(new String[modified.size()])); project.setDescription(desc, new NullProgressMonitor()); } return project; } /** * Handle deleting the stale project if it exists. * * @param name The project name. * @param folder The project folder. */ protected static void deleteStaleProject(String name, String folder) throws Exception { // check for same project location w/ diff project name, or a stale // .project file. File projectFile = new File(folder + File.separator + ".project"); if(projectFile.exists()){ if(xpath == null){ xpath = XmlUtils.createXPathExpression( "/projectDescription/name/text()"); factory = DocumentBuilderFactory.newInstance(); } Document document = factory.newDocumentBuilder().parse(projectFile); String projectName = (String)xpath.evaluate(document); if(!projectName.equals(name)){ IProject project = ProjectUtils.getProject(projectName); if(project.exists()){ project.delete(false/*deleteContent*/, true/*force*/, null/*monitor*/); }else{ projectFile.delete(); } } } } /** * Updates a project. * * @param project The project. * @param commandLine The command line for the project create command. */ public static List<Error> update(IProject project, CommandLine commandLine) throws Exception { ProjectUtils.assertExists(project); ArrayList<Error> errors = new ArrayList<Error>(); for (String nature : managers.keySet()){ if(project.hasNature(nature)){ ProjectManager manager = ProjectManagement.getProjectManager(nature); List<Error> errs = manager.update(project, commandLine); if(errs != null){ errors.addAll(errs); } } } return errors; } /** * Removes the nature(s) from a project that this manager manages, or deletes * the project if no other natures exist for the project. * * @param project The project. * @param commandLine The command line for the project delete command. */ public static void delete(IProject project, CommandLine commandLine) throws Exception { ProjectUtils.assertExists(project); try{ if(!project.isOpen()){ project.open(null); } Preferences.getInstance().clearProjectValueCache(project); for (String nature : managers.keySet()){ if(project.hasNature(nature)){ ProjectManager manager = ProjectManagement.getProjectManager(nature); manager.delete(project, commandLine); } } }catch(Exception e){ logger.debug("Failed to perform nature level delete.", e); }finally{ project.delete(false/*deleteContent*/, true/*force*/, null/*monitor*/); } } /** * Refreshes a project by synchronizing it against the files on disk. * * @param project The project. * @param commandLine The command line for the project refresh command. */ public static void refresh(IProject project, CommandLine commandLine) throws Exception { refresh(project, commandLine, true); } private static void refresh( IProject project, CommandLine commandLine, boolean refreshNatures) throws Exception { ProjectUtils.assertExists(project); project.refreshLocal(IResource.DEPTH_INFINITE, null); if (refreshNatures){ for (String nature : managers.keySet()){ if(project.hasNature(nature)){ ProjectManager manager = ProjectManagement.getProjectManager(nature); manager.refresh(project, commandLine); } } } Preferences.getInstance().clearProjectValueCache(project); } }