/******************************************************************************* * 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.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URI; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.AssertionFailedException; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.core.CompilationUnit; import org.grails.ide.eclipse.core.GrailsCoreActivator; import org.grails.ide.eclipse.core.internal.GrailsNature; import org.grails.ide.eclipse.core.internal.plugins.GrailsCore; import org.grails.ide.eclipse.runtime.shared.DependencyData; /** * @author Nieraj Singh * @author Andrew Eisenberg * @author Kris De Volder */ public class GrailsPluginUtil { private static final String BUILD_CONFIG_LOCATION = "grails-app/conf/BuildConfig.groovy"; private GrailsPluginUtil() { // Util class } /** * Returns true iff this file system location is inside the workspace * @param fileSystemLocation * @return */ public static boolean isLocationInWorkspace(String fileSystemLocation) { return findContainingProject(fileSystemLocation) != null; } /** * Finds the corresponding project for a file system location, or null * if it is outside the workspace. * @param fileSystemLocation * @return containing project, or null */ public static IProject findContainingProject(String fileSystemLocation) { if (fileSystemLocation == null) { return null; } // see if we can convert the fileSystemLocation to an IResource URI location = new File(fileSystemLocation).toURI(); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); IResource[] maybes = root.findContainersForLocationURI(location); if (maybes.length == 0) { maybes = root.findFilesForLocationURI(location); } if (maybes.length > 0) { return maybes[0].getProject(); } else { return null; } } /** * Creates a dependency from plugin to dependent. The assumptions are that: * * <ul> * <li>Both projects are grails projects</li> * <li>plugin is a Plugin project</li> * <li>an dependency currently does not exist between the two projects</li> * </ul> * * Does <em>not</em> call refresh dependencies after completing. * * @param dependent * @param plugin * @throws AssertionFailedException if either project is not a grails project, or if plugin is not a plugin project * @throws CoreException if the BuildConfig.groovy file cannot be read */ public static IStatus addPluginDependency(IProject dependent, IProject plugin) throws CoreException { return addPluginDependency(dependent, plugin, false); } /** * This method is not meant for public use. Only for testing. use {@link #addPluginDependency(IProject, IProject)} instead. * If doMangle is true, then add an extra space in the added text so that it cannot be removed. */ public static IStatus addPluginDependency(IProject dependent, IProject plugin, boolean doMangle) throws CoreException { Assert.isTrue(GrailsNature.isGrailsProject(dependent), dependent.getName() + " is not a grails project"); Assert.isTrue(GrailsNature.isGrailsPluginProject(plugin), plugin.getName() + " is not a grails plugin project"); IFile buildConfigFile = dependent.getFile(BUILD_CONFIG_LOCATION); // next create the text to add to BuildConfig.groovy String textToAdd = createDependencyText(dependent, plugin, doMangle); if (!dependencyExists(buildConfigFile, textToAdd)) { InputStream stream = createInputStream(dependent, textToAdd); buildConfigFile.appendContents(stream, true, true, null); return Status.OK_STATUS; } else { return new Status(IStatus.WARNING, GrailsCoreActivator.PLUGIN_ID, "Could not add a dependency from " + dependent.getName() + " to in place plugin " + plugin.getName() + ". Try manually editing the BuildConfig.groovy file."); } } /** * Removes a dependency from plugin to dependent. The assumptions are that: * * <ul> * <li>Both projects are grails projects</li> * <li>plugin is a Plugin project</li> * <li>an dependency currently does exist between the two projects</li> * </ul> * * Does <em>not</em> call refresh dependencies after completing. * * @param dependent * @param plugin * @param doMangle * @throws AssertionFailedException if either project is not a grails project, or if plugin is not a plugin project * @throws CoreException if the BuildConfig.groovy file cannot be read */ public static IStatus removePluginDependency(IProject dependent, IProject plugin) throws CoreException { return removePluginDependency(dependent, plugin, false); } /** * This method is not meant for public use. Only for testing. use {@link #removePluginDependency(IProject, IProject)} instead. * If doMangle is true, then add an extra space in the text to remove so that previously mangled plugin entries can be removed */ public static IStatus removePluginDependency(IProject dependent, IProject plugin, boolean doMangle) throws CoreException { Assert.isTrue(GrailsNature.isGrailsProject(dependent), dependent.getName() + " is not a grails project"); Assert.isTrue(GrailsNature.isGrailsPluginProject(plugin), plugin.getName() + " is not a grails plugin project"); IFile buildConfigFile = dependent.getFile(BUILD_CONFIG_LOCATION); String textToRemove = createDependencyText(dependent, plugin, doMangle); if (dependencyExists(buildConfigFile, textToRemove)) { char[] contents = ((CompilationUnit) JavaCore.create(buildConfigFile)).getContents(); String newText = String.valueOf(contents).replace(textToRemove, ""); InputStream stream = createInputStream(dependent, newText); buildConfigFile.setContents(stream, true, true, null); return Status.OK_STATUS; } else { return new Status(IStatus.WARNING, GrailsCoreActivator.PLUGIN_ID, "Could not remove a dependency from " + dependent.getName() + " to in place plugin " + plugin.getName() + ". Try manually editing the BuildConfig.groovy file."); } } public static boolean dependencyExists(IProject dependent, IProject plugin) throws CoreException { IFile buildConfigFile = dependent.getFile(BUILD_CONFIG_LOCATION); String dependencyText = createDependencyText(dependent, plugin, false); return dependencyExists(buildConfigFile, dependencyText); } private static boolean dependencyExists(IFile buildConfigFile, String dependencyText) throws CoreException { char[] contents = ((CompilationUnit) JavaCore.create(buildConfigFile)).getContents(); return CharOperation.indexOf(dependencyText.toCharArray(), contents, true) >= 0; } private static InputStream createInputStream(IProject dependent, String textToAdd) throws CoreException { String encoding = JavaCore.create(dependent).getOption(JavaCore.CORE_ENCODING, true); InputStream stream; try { stream = new ByteArrayInputStream(encoding == null ? textToAdd .getBytes() : textToAdd.getBytes(encoding)); } catch (UnsupportedEncodingException e) { throw new CoreException(new Status(IStatus.ERROR, GrailsCoreActivator.PLUGIN_ID, IStatus.ERROR, "failed to add plugin dependency", e)); } return stream; } /** * Creates the text that will be added to the BuildConfig.groovy file * @param dependent the target grails project * @param plugin the plugin project that will be depended on * @param doMangle if true, add an extra space in the returned text * @return A string containing the "grails.plugin.location" property for the dependent project */ private static String createDependencyText(IProject dependent, IProject plugin, boolean doMangle) { // if plugin path and dependent path are both in same directory use relative path // otherwise, use full path. IPath pathToPluginProject = plugin.getLocation(); IPath pathToDependentProject = dependent.getLocation(); boolean isColocated = pathToDependentProject.segmentCount() == pathToPluginProject.segmentCount() && pathToDependentProject.removeLastSegments(1).isPrefixOf(pathToPluginProject); String textToAdd = "grails.plugin.location." + maybeQuote(plugin.getName()) + (doMangle ? " " : "") + " = \"" + (isColocated ? "../" + pathToPluginProject.lastSegment() : pathToPluginProject.toPortableString()) + "\"\n"; return textToAdd; } private static String maybeQuote(String name) { return name.contains("-") ? "'" + name + "'" : name; } /** * Returns the location of the plugins directory inside the .grails folder. This information is extracted from * our dependency data file. So to ensure this info is up-to-date the file must be up-to-date. The file gets generated * by refreshDependencies, or by any GrailsCommand executed with "enableRefreshDependencyFile". * <p> * Callers of this method are responsible for ensuring that the file is up-to-date. * * @param project */ public static String getPluginsDirectory(IProject project) { PerProjectDependencyDataCache info = GrailsCore.get().connect(project, PerProjectDependencyDataCache.class); if (info!=null) { DependencyData data = info.getData(); if (data!=null) { return data.getPluginsDirectory(); } } return null; } /** * The Grails work Directory as (for example) set on commandLine by -Dgrails.work.dir. Typically this * is a folder in the user's .grails folder. * <p> * This information is extracted from our dependency data file. So to ensure this info is up-to-date the * file must be up-to-date. The file gets generated by refreshDependencies (or by any GrailsCommand executed * with "enableRefreshDependencyFile" turned on. * <p> * Callers of this method are responsible for ensuring that the file is up-to-date. */ public static String getGrailsWorkDir(IProject project) { PerProjectDependencyDataCache info = GrailsCore.get().connect(project, PerProjectDependencyDataCache.class); if (info != null) { DependencyData data = info.getData(); if (data != null) { return data.getWorkDir(); } } return null; } public static boolean isInPlacePluginDescriptor(String descriptor) { if (descriptor!=null) { URI location = new File(descriptor).toURI(); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); IResource[] maybes = root.findContainersForLocationURI(location); if (maybes.length>0) { //It is a location in the workspace (or several locations :-0 //So maybe it is an inplace plugin... except if.. for (IResource iResource : maybes) { if (iResource.getFullPath().toString().contains(SourceFolderJob.PLUGINS_FOLDER_LINK)) { //Stuff inside this special hidden linked folder are externally installed by Grails return false; } } return true; } } return false; } }