/**
* 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.dltk.project;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.OptionBuilder;
import org.eclim.Services;
import org.eclim.command.CommandLine;
import org.eclim.command.Error;
import org.eclim.command.Options;
import org.eclim.plugin.core.project.ProjectManager;
import org.eclim.plugin.core.util.ProjectUtils;
import org.eclim.plugin.core.util.XmlUtils;
import org.eclim.plugin.dltk.PluginResources;
import org.eclim.util.IOUtils;
import org.eclim.util.StringUtils;
import org.eclim.util.file.FileOffsets;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.DLTKLanguageManager;
import org.eclipse.dltk.core.IBuildpathEntry;
import org.eclipse.dltk.core.IDLTKLanguageToolkit;
import org.eclipse.dltk.core.IModelStatus;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.SourceParserUtil;
import org.eclipse.dltk.internal.core.BuildpathEntry;
import org.eclipse.dltk.internal.ui.wizards.BuildpathDetector;
import org.eclipse.dltk.launching.IInterpreterInstall;
import org.eclipse.dltk.launching.IInterpreterInstallType;
import org.eclipse.dltk.launching.ScriptRuntime;
/**
* Implementation of {@link ProjectManager} for dltk projects.
*
* @author Eric Van Dewoestine
*/
public abstract class DltkProjectManager
implements ProjectManager
{
private static final String BUILDPATH = ".buildpath";
private static final String BUILDPATH_XSD =
"/resources/schema/eclipse/buildpath.xsd";
@Override
@SuppressWarnings("static-access")
public void create(IProject project, CommandLine commandLine)
throws Exception
{
String[] args = commandLine.getValues(Options.ARGS_OPTION);
GnuParser parser = new GnuParser();
org.apache.commons.cli.Options options = new org.apache.commons.cli.Options();
options.addOption(OptionBuilder.hasArg().withLongOpt("interpreter").create());
org.apache.commons.cli.CommandLine cli = parser.parse(options, args);
IInterpreterInstall interpreter = null;
if (cli.hasOption("interpreter")){
String interpreterName = cli.getOptionValue("interpreter");
IInterpreterInstallType[] types =
ScriptRuntime.getInterpreterInstallTypes(getNatureId());
loop: for (IInterpreterInstallType type : types){
IInterpreterInstall[] installs = type.getInterpreterInstalls();
for (IInterpreterInstall install : installs){
if (install.getName().equals(interpreterName)){
interpreter = install;
break loop;
}
}
}
if (interpreter == null){
throw new IllegalArgumentException(Services.getMessage(
"interpreter.name.not.found", interpreterName));
}
}
String dependsString = commandLine.getValue(Options.DEPENDS_OPTION);
IScriptProject scriptProject = DLTKCore.create(project);
if (!project.getFile(BUILDPATH).exists()) {
IDLTKLanguageToolkit toolkit = getLanguageToolkit(getNatureId());
BuildpathDetector detector = new BuildpathDetector(project, toolkit);
detector.detectBuildpath(null);
IBuildpathEntry[] detected = detector.getBuildpath();
// remove any entries the detector may have added that are not valid for
// this project (currently happens on php projects with the
// org.eclipse.dltk.launching.INTERPRETER_CONTAINER entry).
ArrayList<IBuildpathEntry> entries = new ArrayList<IBuildpathEntry>();
for (IBuildpathEntry entry : detected){
IModelStatus status = BuildpathEntry
.validateBuildpathEntry(scriptProject, entry, true);
if(status.isOK()){
entries.add(entry);
}
}
detected = entries.toArray(new IBuildpathEntry[entries.size()]);
IBuildpathEntry[] depends =
createOrUpdateDependencies(scriptProject, dependsString);
IBuildpathEntry[] buildpath = merge(
new IBuildpathEntry[][]{detected, depends});
//scriptProject.readRawClasspath(), detected, depends, container
scriptProject.setRawBuildpath(buildpath, null);
}
if (interpreter != null){
IBuildpathEntry[] buildpath = scriptProject.getRawBuildpath();
int containerIndex = 0;
for (int i = 0; i < buildpath.length; i++){
if (buildpath[i].getEntryKind() == IBuildpathEntry.BPE_CONTAINER){
containerIndex = i;
break;
}
}
if (containerIndex == 0){
throw new RuntimeException("No container buildpath entry found.");
}
IBuildpathEntry container = buildpath[containerIndex];
buildpath[containerIndex] = DLTKCore.newContainerEntry(
ScriptRuntime.newInterpreterContainerPath(interpreter),
container.getAccessRules(),
container.getExtraAttributes(),
container.isExported());
scriptProject.setRawBuildpath(buildpath, null);
}
scriptProject.makeConsistent(null);
scriptProject.save(null, false);
}
@Override
public List<Error> update(IProject project, CommandLine commandLine)
throws Exception
{
IScriptProject scriptProject = DLTKCore.create(project);
scriptProject.getResource().refreshLocal(IResource.DEPTH_INFINITE, null);
// validate that .buildpath xml is well formed and valid.
PluginResources resources = (PluginResources)
Services.getPluginResources(PluginResources.NAME);
List<Error> errors = XmlUtils.validateXml(
scriptProject.getProject().getName(),
BUILDPATH,
resources.getResource(BUILDPATH_XSD).toString());
if(errors.size() > 0){
return errors;
}
String dotbuildpath = scriptProject.getProject().getFile(BUILDPATH)
.getRawLocation().toOSString();
IBuildpathEntry[] entries = scriptProject.readRawBuildpath();
FileOffsets offsets = FileOffsets.compile(dotbuildpath);
String buildpath = IOUtils.toString(new FileInputStream(dotbuildpath));
errors = new ArrayList<Error>();
for(IBuildpathEntry entry : entries){
IModelStatus status = BuildpathEntry.validateBuildpathEntry(
scriptProject, entry, true);
if(!status.isOK()){
errors.add(createErrorForEntry(
entry, status, offsets, dotbuildpath, buildpath));
}
}
// always set the buildpath anyways, so that the user can correct the file.
//if(status.isOK() && errors.isEmpty()){
scriptProject.setRawBuildpath(entries, null);
scriptProject.makeConsistent(null);
//}
if(errors.size() > 0){
return errors;
}
return null;
}
@Override
public void delete(IProject project, CommandLine commandLine)
throws Exception
{
}
@Override
public void refresh(IProject project, CommandLine commandLine)
throws Exception
{
SourceParserUtil.clearCache();
}
@Override
public void refresh(IProject project, IFile file)
throws Exception
{
}
/**
* Get the language toolkit to use.
*
* @param natureId The nature id to get the toolkit for.
* @return The IDLTKLanguageToolkit to use.
*/
public IDLTKLanguageToolkit getLanguageToolkit(String natureId)
{
return DLTKLanguageManager.getLanguageToolkit(natureId);
}
/**
* Abstract method for subclasses to override which provides the appropriate
* dltk nature id.
*
* @return The nature.
*/
public abstract String getNatureId();
/**
* Creates an Error from the supplied IModelStatus.
*
* @param entry The build path entry.
* @param status The IModelStatus.
* @param offsets File offsets for the buildpath file.
* @param filename The filename of the error.
* @param contents The contents of the file as a String.
* @return The Error.
*/
protected Error createErrorForEntry(
IBuildpathEntry entry,
IModelStatus status,
FileOffsets offsets,
String filename,
String contents)
throws Exception
{
int line = 1;
int col = 1;
String path = entry.getPath().toOSString();
Matcher matcher =
Pattern.compile("path\\s*=(['\"])\\s*\\Q" + path + "\\E\\s*\\1")
.matcher(contents);
if(matcher.find()){
int[] position = offsets.offsetToLineColumn(matcher.start());
line = position[0];
col = position[1];
}
return new Error(status.getMessage(), filename, line, col, false);
}
/**
* Creates or updates the projects dependencies on other projects.
*
* @param project The project.
* @param depends The comma seperated list of project names.
*/
protected IBuildpathEntry[] createOrUpdateDependencies(
IScriptProject project, String depends)
throws Exception
{
if(depends != null){
String[] dependPaths = StringUtils.split(depends, ',');
IBuildpathEntry[] entries = new IBuildpathEntry[dependPaths.length];
for(int ii = 0; ii < dependPaths.length; ii++){
IProject theProject = ProjectUtils.getProject(dependPaths[ii]);
if(!theProject.exists()){
throw new IllegalArgumentException(Services.getMessage(
"project.depends.not.found", dependPaths[ii]));
}
IScriptProject otherProject = DLTKCore.create(theProject);
entries[ii] = DLTKCore.newProjectEntry(otherProject.getPath(), true);
}
return entries;
}
return new IBuildpathEntry[0];
}
/**
* Merges the supplied buildpath entries into one.
*
* @param entries The array of buildpath entry arrays to merge.
*
* @return The union of all entry arrays.
*/
protected IBuildpathEntry[] merge(IBuildpathEntry[][] entries)
{
ArrayList<IBuildpathEntry> union = new ArrayList<IBuildpathEntry>();
if(entries != null){
for(IBuildpathEntry[] values : entries){
if(values != null){
for(IBuildpathEntry entry : values){
if(!union.contains(entry)){
union.add(entry);
}
}
}
}
}
return (IBuildpathEntry[])union.toArray(new IBuildpathEntry[union.size()]);
}
}