blob: b02ba7a9c16165c8965cf29bcbd489804038d1b0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ant.internal.ui;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.util.FileUtils;
import org.eclipse.ant.core.AntCorePlugin;
import org.eclipse.ant.internal.core.AntCoreUtil;
import org.eclipse.ant.internal.ui.editor.AntEditor;
import org.eclipse.ant.internal.ui.launchConfigurations.AntHomeClasspathEntry;
import org.eclipse.ant.internal.ui.launchConfigurations.IAntLaunchConfigurationConstants;
import org.eclipse.ant.internal.ui.model.AntElementNode;
import org.eclipse.ant.internal.ui.model.AntModel;
import org.eclipse.ant.internal.ui.model.AntProjectNode;
import org.eclipse.ant.internal.ui.model.AntTargetNode;
import org.eclipse.ant.internal.ui.model.IAntModel;
import org.eclipse.ant.internal.ui.model.LocationProvider;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.ui.console.FileLink;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry2;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.SWT;
import org.eclipse.swt.program.Program;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorRegistry;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.console.IHyperlink;
import org.eclipse.ui.externaltools.internal.launchConfigurations.ExternalToolsUtil;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
/**
* General utility class dealing with Ant build files
*/
public final class AntUtil {
public static final String ATTRIBUTE_SEPARATOR = ","; //$NON-NLS-1$;
public static final char ANT_CLASSPATH_DELIMITER= '*';
public static final String ANT_HOME_CLASSPATH_PLACEHOLDER= "G"; //$NON-NLS-1$
public static final String ANT_GLOBAL_USER_CLASSPATH_PLACEHOLDER= "UG"; //$NON-NLS-1$
/**
* No instances allowed
*/
private AntUtil() {
super();
}
/**
* Returns a single-string of the strings for storage.
*
* @param strings the array of strings
* @return a single-string representation of the strings or
* <code>null</code> if the array is empty.
*/
public static String combineStrings(String[] strings) {
if (strings.length == 0)
return null;
if (strings.length == 1)
return strings[0];
StringBuffer buf = new StringBuffer();
for (int i = 0; i < strings.length - 1; i++) {
buf.append(strings[i]);
buf.append(ATTRIBUTE_SEPARATOR);
}
buf.append(strings[strings.length - 1]);
return buf.toString();
}
/**
* Returns an array of targets to be run, or <code>null</code> if none are
* specified (indicating the default target should be run).
*
* @param configuration launch configuration
* @return array of target names, or <code>null</code>
* @throws CoreException if unable to access the associated attribute
*/
public static String[] getTargetNames(ILaunchConfiguration configuration) throws CoreException {
String attribute = configuration.getAttribute(IAntLaunchConfigurationConstants.ATTR_ANT_TARGETS, (String) null);
if (attribute == null) {
return null;
}
return AntUtil.parseRunTargets(attribute);
}
/**
* Returns a map of properties to be defined for the build, or
* <code>null</code> if none are specified (indicating no additional
* properties specified for the build).
*
* @param configuration launch configuration
* @return map of properties (name --> value), or <code>null</code>
* @throws CoreException if unable to access the associated attribute
*/
public static Map getProperties(ILaunchConfiguration configuration) throws CoreException {
Map map = configuration.getAttribute(IAntLaunchConfigurationConstants.ATTR_ANT_PROPERTIES, (Map) null);
return map;
}
/**
* Returns a String specifying the Ant home to use for the build, or
* <code>null</code> if none is specified.
*
* @param configuration launch configuration
* @return String specifying Ant home to use, or <code>null</code>
* @throws CoreException if unable to access the associated attribute
*/
public static String getAntHome(ILaunchConfiguration configuration) throws CoreException {
IRuntimeClasspathEntry[] entries = JavaRuntime.computeUnresolvedRuntimeClasspath(configuration);
for (int i = 0; i < entries.length; i++) {
IRuntimeClasspathEntry entry = entries[i];
if (entry.getType() == IRuntimeClasspathEntry.OTHER) {
IRuntimeClasspathEntry2 entry2 = (IRuntimeClasspathEntry2)entry;
if (entry2.getTypeId().equals(AntHomeClasspathEntry.TYPE_ID)) {
return ((AntHomeClasspathEntry)entry2).getAntHome();
}
}
}
return null;
}
/**
* Returns an array of property files to be used for the build, or
* <code>null</code> if none are specified (indicating no additional
* property files specified for the build).
*
* @param configuration launch configuration
* @return array of property file names, or <code>null</code>
* @throws CoreException if unable to access the associated attribute
*/
public static String[] getPropertyFiles(ILaunchConfiguration configuration) throws CoreException {
String attribute = configuration.getAttribute(IAntLaunchConfigurationConstants.ATTR_ANT_PROPERTY_FILES, (String) null);
if (attribute == null) {
return null;
}
String[] propertyFiles= AntUtil.parseString(attribute, ","); //$NON-NLS-1$
for (int i = 0; i < propertyFiles.length; i++) {
String propertyFile = propertyFiles[i];
propertyFile= expandVariableString(propertyFile, AntUIModelMessages.getString("AntUtil.6")); //$NON-NLS-1$ //$NON-NLS-2$
propertyFiles[i]= propertyFile;
}
return propertyFiles;
}
public static AntTargetNode[] getTargets(String path, ILaunchConfiguration config) throws CoreException {
File buildfile= getBuildFile(path);
if (buildfile == null) {
return null;
}
URL[] urls= getCustomClasspath(config);
//no lexical, no position, no task
IAntModel model= getAntModel(buildfile, urls, false, false, false);
model.setProperties(getAllProperties(config));
model.setPropertyFiles(getPropertyFiles(config));
AntProjectNode project= model.getProjectNode(); //forces a reconcile
model.dispose();
return getTargets(project);
}
private static Map getAllProperties(ILaunchConfiguration config) throws CoreException {
String[] arguments = ExternalToolsUtil.getArguments(config);
Map properties= new HashMap();
if (arguments != null) {
AntCoreUtil.processMinusDProperties(AntCoreUtil.getArrayList(arguments), properties);
}
Map configProperties= getProperties(config);
if (configProperties != null) {
Iterator keys= configProperties.keySet().iterator();
while (keys.hasNext()) {
String name = (String) keys.next();
if (properties.get(name) == null) {
properties.put(name, configProperties.get(name));
}
}
}
return properties;
}
private static AntTargetNode[] getTargets(AntProjectNode project) {
if (project == null || !project.hasChildren()) {
return null;
}
List targets= new ArrayList();
Iterator possibleTargets= project.getChildNodes().iterator();
while (possibleTargets.hasNext()) {
AntElementNode node= (AntElementNode)possibleTargets.next();
if (node instanceof AntTargetNode) {
targets.add(node);
}
}
if (targets.size() == 0) {
return null;
}
return (AntTargetNode[])targets.toArray(new AntTargetNode[targets.size()]);
}
public static AntTargetNode[] getTargets(String path) {
File buildfile= getBuildFile(path);
if (buildfile == null) {
return null;
}
//tasks and position info but no lexical info
IAntModel model= getAntModel(buildfile, null, false, true, true);
AntProjectNode project= model.getProjectNode();
if (project == null) {
model.dispose();
return null;
}
return getTargets(project);
}
public static IAntModel getAntModel(String buildFilePath, boolean needsLexicalResolution, boolean needsPositionResolution, boolean needsTaskResolution) {
IAntModel model= getAntModel(getBuildFile(buildFilePath), null, needsLexicalResolution, needsPositionResolution, needsTaskResolution);
return model;
}
/**
* Return a buildfile from the specified location.
* If there isn't one return null.
*/
private static File getBuildFile(String path) {
File buildFile = new File(path);
if (!buildFile.isFile() || !buildFile.exists()) {
return null;
}
return buildFile;
}
private static IAntModel getAntModel(final File buildFile, URL[] urls, boolean needsLexical, boolean needsPosition, boolean needsTask) {
if (buildFile == null || !buildFile.exists()) {
return null;
}
IDocument doc= getDocument(buildFile);
if (doc == null) {
return null;
}
final IFile file= getFileForLocation(buildFile.getAbsolutePath(), null);
LocationProvider provider= new LocationProvider(null) {
/* (non-Javadoc)
* @see org.eclipse.ant.internal.ui.model.LocationProvider#getFile()
*/
public IFile getFile() {
return file;
}
/* (non-Javadoc)
* @see org.eclipse.ant.internal.ui.model.LocationProvider#getLocation()
*/
public IPath getLocation() {
if (file == null) {
return new Path(buildFile.getAbsolutePath());
}
return file.getLocation();
}
};
IAntModel model= new AntModel(doc, null, provider, needsLexical, needsPosition, needsTask);
if (urls != null) {
model.setClassLoader(AntCorePlugin.getPlugin().getNewClassLoader(urls));
}
return model;
}
private static IDocument getDocument(File buildFile) {
InputStream in= null;
try {
in = new FileInputStream(buildFile);
String content= getStreamContentAsString(in);
if (content != null) {
return new Document(content);
}
return null;
} catch (FileNotFoundException e) {
return null;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException x) {
}
}
}
}
private static String getStreamContentAsString(InputStream inputStream) {
InputStreamReader reader;
try {
reader = new InputStreamReader(inputStream, ResourcesPlugin.getEncoding());
} catch (UnsupportedEncodingException e) {
AntUIPlugin.log(e);
return ""; //$NON-NLS-1$
}
BufferedReader bufferedReader= null;
try {
bufferedReader= new BufferedReader(reader);
return getReaderContentAsString(bufferedReader);
} finally {
try {
bufferedReader.close();
} catch (IOException e1) {
}
}
}
private static String getReaderContentAsString(BufferedReader bufferedReader) {
StringBuffer result = new StringBuffer();
try {
char[] readBuffer= new char[2048];
int n= bufferedReader.read(readBuffer);
while (n > 0) {
result.append(readBuffer, 0, n);
n= bufferedReader.read(readBuffer);
}
} catch (IOException e) {
AntUIPlugin.log(e);
return null;
}
return result.toString();
}
/**
* Returns the list of urls that define the custom classpath for the Ant
* build, or <code>null</code> if the global classpath is to be used.
*
* @param config launch configuration
* @return a list of <code>URL</code>
*
* @throws CoreException if file does not exist, IO problems, or invalid format.
*/
public static URL[] getCustomClasspath(ILaunchConfiguration config) throws CoreException {
boolean useDefault = config.getAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_CLASSPATH, true);
if (useDefault) {
return null;
}
IRuntimeClasspathEntry[] unresolved = JavaRuntime.computeUnresolvedRuntimeClasspath(config);
// don't consider bootpath entries
List userEntries = new ArrayList(unresolved.length);
for (int i = 0; i < unresolved.length; i++) {
IRuntimeClasspathEntry entry = unresolved[i];
if (entry.getClasspathProperty() == IRuntimeClasspathEntry.USER_CLASSES) {
userEntries.add(entry);
}
}
IRuntimeClasspathEntry[] entries = JavaRuntime.resolveRuntimeClasspath((IRuntimeClasspathEntry[])userEntries.toArray(new IRuntimeClasspathEntry[userEntries.size()]), config);
URL[] urls = new URL[entries.length];
for (int i = 0; i < entries.length; i++) {
IRuntimeClasspathEntry entry = entries[i];
try {
urls[i] = new URL("file:"+entry.getLocation()); //$NON-NLS-1$
} catch (MalformedURLException e) {
throw new CoreException(new Status(IStatus.ERROR, AntUIPlugin.getUniqueIdentifier(), AntUIPlugin.INTERNAL_ERROR, AntUIModelMessages.getString("AntUtil.7"), e)); //$NON-NLS-1$
}
}
return urls;
}
private static String expandVariableString(String variableString, String invalidMessage) throws CoreException {
String expandedString = VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(variableString);
if (expandedString == null || expandedString.length() == 0) {
String msg = MessageFormat.format(invalidMessage, new String[] {variableString});
throw new CoreException(new Status(IStatus.ERROR, IAntUIConstants.PLUGIN_ID, 0, msg, null));
}
return expandedString;
}
/**
* Returns the list of target names to run
*
* @param extraAttibuteValue the external tool's extra attribute value
* for the run targets key.
* @return a list of target names
*/
public static String[] parseRunTargets(String extraAttibuteValue) {
return parseString(extraAttibuteValue, ATTRIBUTE_SEPARATOR);
}
/**
* Returns the list of Strings that were delimiter separated.
*
* @param delimString the String to be tokenized based on the delimiter
* @return a list of Strings
*/
public static String[] parseString(String delimString, String delim) {
if (delimString == null) {
return new String[0];
}
// Need to handle case where separator character is
// actually part of the target name!
StringTokenizer tokenizer = new StringTokenizer(delimString, delim);
String[] results = new String[tokenizer.countTokens()];
for (int i = 0; i < results.length; i++) {
results[i] = tokenizer.nextToken();
}
return results;
}
/**
* Returns an IFile with the given fully qualified path (relative to the workspace root).
* The returned IFile may or may not exist.
*/
public static IFile getFile(String fullPath) {
IWorkspaceRoot root= ResourcesPlugin.getWorkspace().getRoot();
return root.getFile(new Path(fullPath));
}
public static IHyperlink getTaskLink(String path, File buildFileParent) {
path = path.trim();
if (path.length() == 0) {
return null;
}
if (path.startsWith("file:")) { //$NON-NLS-1$
// remove "file:"
path= path.substring(5, path.length());
}
// format is file:F:L: where F is file path, and L is line number
int index = path.lastIndexOf(':');
if (index == path.length() - 1) {
// remove trailing ':'
path = path.substring(0, index);
index = path.lastIndexOf(':');
}
// split file and line number
String fileName = path.substring(0, index);
IFile file = getFileForLocation(fileName, buildFileParent);
if (file != null) {
try {
String lineNumber = path.substring(index + 1);
int line = Integer.parseInt(lineNumber);
return new FileLink(file, null, -1, -1, line);
} catch (NumberFormatException e) {
}
}
return null;
}
/**
* Returns the workspace file associated with the given path in the
* local file system, or <code>null</code> if none.
* If the path happens to be a relative path, then the path is interpreted as
* relative to the specified parent file.
*
* Attempts to handle linked files; the first found linked file with the correct
* path is returned.
*
* @param path
* @param buildFileParent
* @return file or <code>null</code>
* @see org.eclipse.core.resources.IWorkspaceRoot#findFilesForLocation(IPath)
*/
public static IFile getFileForLocation(String path, File buildFileParent) {
IPath filePath= new Path(path);
IFile file = null;
IFile[] files= ResourcesPlugin.getWorkspace().getRoot().findFilesForLocation(filePath);
if (files.length > 0) {
file= files[0];
}
if (file == null) {
//relative path
File relativeFile= null;
try {
//this call is ok if buildFileParent is null
relativeFile= FileUtils.newFileUtils().resolveFile(buildFileParent, path);
filePath= new Path(relativeFile.getAbsolutePath());
files= ResourcesPlugin.getWorkspace().getRoot().findFilesForLocation(filePath);
if (files.length > 0) {
file= files[0];
} else {
return null;
}
} catch (BuildException be) {
return null;
}
}
if (file.exists()) {
return file;
}
File ioFile= file.getLocation().toFile();
if (ioFile.exists()) {//needs to handle case insensitivity on WINOS
try {
files= ResourcesPlugin.getWorkspace().getRoot().findFilesForLocation(new Path(ioFile.getCanonicalPath()));
if (files.length > 0) {
return files[0];
}
} catch (IOException e) {
}
}
return null;
}
/**
* Migrates the classpath in the given configuration from the old format
* to the new foramt. The old format is not preserved. Instead, the default
* classpath will be used. However, ANT_HOME settings are preserved.
*
* @param configuration a configuration to migrate
* @throws CoreException if unable to migrate
* @since 3.0
*/
public static void migrateToNewClasspathFormat(ILaunchConfiguration configuration) throws CoreException {
String oldClasspath = configuration.getAttribute(IAntLaunchConfigurationConstants.ATTR_ANT_CUSTOM_CLASSPATH, (String)null);
String oldAntHome = configuration.getAttribute(IAntLaunchConfigurationConstants.ATTR_ANT_HOME, (String)null);
String provider = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_CLASSPATH_PROVIDER, (String)null);
if (oldClasspath != null || oldAntHome != null || provider == null) {
ILaunchConfigurationWorkingCopy workingCopy = null;
if (configuration.isWorkingCopy()) {
workingCopy = (ILaunchConfigurationWorkingCopy) configuration;
} else {
workingCopy = configuration.getWorkingCopy();
}
workingCopy.setAttribute(IAntLaunchConfigurationConstants.ATTR_ANT_CUSTOM_CLASSPATH, (String)null);
workingCopy.setAttribute(IAntLaunchConfigurationConstants.ATTR_ANT_HOME, (String)null);
workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_CLASSPATH_PROVIDER, "org.eclipse.ant.ui.AntClasspathProvider"); //$NON-NLS-1$
workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_CLASSPATH, true);
if (oldAntHome != null) {
IRuntimeClasspathEntry[] entries = JavaRuntime.computeUnresolvedRuntimeClasspath(workingCopy);
List mementos = new ArrayList(entries.length);
for (int i = 0; i < entries.length; i++) {
IRuntimeClasspathEntry entry = entries[i];
if (entry.getType() == IRuntimeClasspathEntry.OTHER) {
IRuntimeClasspathEntry2 entry2 = (IRuntimeClasspathEntry2) entry;
if (entry2.getTypeId().equals(AntHomeClasspathEntry.TYPE_ID)) {
AntHomeClasspathEntry homeEntry = new AntHomeClasspathEntry(oldAntHome);
mementos.add(homeEntry.getMemento());
continue;
}
}
mementos.add(entry.getMemento());
}
workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_DEFAULT_CLASSPATH, false);
workingCopy.setAttribute(IJavaLaunchConfigurationConstants.ATTR_CLASSPATH, mementos);
}
workingCopy.doSave();
}
}
private static int getOffset(int line, int column, ITextEditor editor) {
IDocumentProvider provider= editor.getDocumentProvider();
IEditorInput input= editor.getEditorInput();
try {
provider.connect(input);
} catch (CoreException e) {
return -1;
}
try {
IDocument document= provider.getDocument(input);
if (document != null) {
if (column > -1) {
//column marks the length..adjust to 0 index and to be within the element's source range
return document.getLineOffset(line - 1) + column - 1 - 2;
}
return document.getLineOffset(line - 1);
}
} catch (BadLocationException e) {
} finally {
provider.disconnect(input);
}
return -1;
}
/**
* Opens the given editor on the buildfile of the provided node and selects that node in the editor.
*
* @param page the page to open the editor in
* @param editor the editor descriptor, or <code>null</code> for the system editor
* @param node the node from the buildfile to open and then select in the editor
*/
public static void openInEditor(IWorkbenchPage page, IEditorDescriptor editorDescriptor, AntElementNode node) {
IEditorPart editorPart= null;
IFile fileResource = node.getIFile();
try {
if (editorDescriptor == null) {
editorPart= page.openEditor(new FileEditorInput(fileResource), IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID);
} else {
editorPart= page.openEditor(new FileEditorInput(fileResource), editorDescriptor.getId());
}
} catch (PartInitException e) {
AntUIPlugin.log(MessageFormat.format(AntUIModelMessages.getString("AntUtil.0"), new String[]{fileResource.getLocation().toOSString()}), e); //$NON-NLS-1$
}
if (editorPart instanceof AntEditor) {
AntEditor editor= (AntEditor)editorPart;
if (node.getImportNode() != null) {
AntModel model= editor.getAntModel();
AntProjectNode project= model.getProjectNode();
if (project == null) {
return;
}
int[] info= node.getExternalInfo();
int offset= getOffset(info[0], info[1], editor);
node= project.getNode(offset);
}
editor.setSelection(node, true);
}
}
/**
* Opens an editor on the buildfile of the provided node and selects that node in the editor.
*
* @param page the page to open the editor in
* @param node the node from the buildfile to open and then select in the editor
*/
public static void openInEditor(IWorkbenchPage page, AntElementNode node) {
IFile file= node.getIFile();
IEditorDescriptor editorDesc;
try {
editorDesc = IDE.getEditorDescriptor(file);
} catch (PartInitException e) {
return;
}
openInEditor(page, editorDesc, node);
}
/**
* Opens an external browser on the provided <code>urlString</code>
* @param urlString The url to open
* @param shell the shell
* @param errorDialogTitle the title of any error dialog
*/
public static void openBrowser(final String urlString, final Shell shell, final String errorDialogTitle) {
shell.getDisplay().syncExec(new Runnable() {
public void run() {
String platform= SWT.getPlatform();
boolean succeeded= true;
if ("motif".equals(platform) || "gtk".equals(platform)) { //$NON-NLS-1$ //$NON-NLS-2$
Program program= Program.findProgram("html"); //$NON-NLS-1$
if (program == null) {
program= Program.findProgram("htm"); //$NON-NLS-1$
}
if (program != null) {
succeeded= program.execute(urlString.toString());
}
} else {
succeeded= Program.launch(urlString.toString());
}
if (!succeeded) {
MessageDialog.openInformation(shell, errorDialogTitle, AntUIModelMessages.getString("AntUtil.1")); //$NON-NLS-1$
}
}
});
}
public static boolean isSeparateJREAntBuild(ILaunchConfiguration configuration) {
boolean separateJRE= true;
try {
//always null for same JRE
separateJRE = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, (String)null) != null;
} catch (CoreException e) {
AntUIPlugin.log(AntUIModelMessages.getString("AntUtil.2"), e); //$NON-NLS-1$
}
return separateJRE;
}
}