blob: 3e8ebd56b615797814959f8c4cb0396526f5fe3a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.ant.internal.ui;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.tools.ant.Target;
import org.eclipse.ant.core.AntCorePlugin;
import org.eclipse.ant.internal.core.AntCoreUtil;
import org.eclipse.ant.internal.core.IAntCoreConstants;
import org.eclipse.ant.internal.launching.AntLaunchingUtil;
import org.eclipse.ant.internal.ui.editor.AntEditor;
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.IAntElement;
import org.eclipse.ant.internal.ui.model.IAntModel;
import org.eclipse.ant.internal.ui.model.LocationProvider;
import org.eclipse.ant.launching.IAntLaunchConstants;
import org.eclipse.core.externaltools.internal.IExternalToolConstants;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.filebuffers.LocationKind;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.ui.console.FileLink;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
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.PlatformUI;
import org.eclipse.ui.browser.IWebBrowser;
import org.eclipse.ui.browser.IWorkbenchBrowserSupport;
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;
import com.ibm.icu.text.MessageFormat;
/**
* 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$
private static String fgBrowserId;
/**
* 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) {
return AntLaunchingUtil.combineStrings(strings);
}
/**
* Returns an array of targets to be run, or <code>null</code> if none are specified (indicating the default target or implicit 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 {
return AntLaunchingUtil.getTargetNames(configuration);
}
/**
* 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<String, String> getProperties(ILaunchConfiguration configuration) throws CoreException {
return AntLaunchingUtil.getProperties(configuration);
}
/**
* Returns a String specifying the Ant home to use for the build.
*
* @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 {
return AntLaunchingUtil.getAntHome(configuration);
}
/**
* 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(IAntLaunchConstants.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.AntUtil_6);
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);
try {
model.setProperties(getAllProperties(config));
}
catch (CoreException ex) {
// do nothing
}
model.setPropertyFiles(getPropertyFiles(config));
AntProjectNode project = model.getProjectNode(); // forces a reconcile
model.dispose();
return getTargets(project);
}
private static Map<String, String> getAllProperties(ILaunchConfiguration config) throws CoreException {
String allArgs = config.getAttribute(IExternalToolConstants.ATTR_TOOL_ARGUMENTS, (String) null);
Map<String, String> properties = new HashMap<>();
if (allArgs != null) {
String[] arguments = ExternalToolsUtil.parseStringIntoList(allArgs);
// filter arguments to avoid resolving variables that will prompt the user
List<String> filtered = new ArrayList<>();
Pattern pattern = Pattern.compile("\\$\\{.*_prompt.*\\}"); //$NON-NLS-1$
IStringVariableManager manager = VariablesPlugin.getDefault().getStringVariableManager();
for (int i = 0; i < arguments.length; i++) {
String arg = arguments[i];
if (arg.startsWith("-D")) { //$NON-NLS-1$
if (!pattern.matcher(arg).find()) {
filtered.add(manager.performStringSubstitution(arg, false));
}
}
}
AntCoreUtil.processMinusDProperties(filtered, properties);
}
Map<String, String> configProperties = getProperties(config);
if (configProperties != null) {
Iterator<String> keys = configProperties.keySet().iterator();
while (keys.hasNext()) {
String name = 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<IAntElement> targets = new ArrayList<>();
for (IAntElement node : project.getChildNodes()) {
if (node instanceof AntTargetNode) {
targets.add(node);
}
}
if (targets.size() == 0) {
return null;
}
return 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;
}
AntTargetNode[] targets = getTargets(project);
if (targets == null) {
Hashtable<String, Target> antTargets = project.getProject().getTargets();
Target implicitTarget = antTargets.get(IAntCoreConstants.EMPTY_STRING);
if (implicitTarget != null) {
AntTargetNode implicitTargetNode = AntTargetNode.newAntTargetNode(implicitTarget);
project.addChildNode(implicitTargetNode);
return new AntTargetNode[] { implicitTargetNode };
}
}
return targets;
}
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()
*/
@Override
public IFile getFile() {
return file;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ant.internal.ui.model.LocationProvider#getLocation()
*/
@Override
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) {
ITextFileBufferManager manager = FileBuffers.getTextFileBufferManager();
IPath location = new Path(buildFile.getAbsolutePath());
boolean connected = false;
try {
ITextFileBuffer buffer = manager.getTextFileBuffer(location, LocationKind.NORMALIZE);
if (buffer == null) {
// no existing file buffer..create one
manager.connect(location, LocationKind.NORMALIZE, new NullProgressMonitor());
connected = true;
buffer = manager.getTextFileBuffer(location, LocationKind.NORMALIZE);
if (buffer == null) {
return null;
}
}
return buffer.getDocument();
}
catch (CoreException ce) {
AntUIPlugin.log(ce.getStatus());
return null;
}
finally {
if (connected) {
try {
manager.disconnect(location, LocationKind.NORMALIZE, new NullProgressMonitor());
}
catch (CoreException e) {
AntUIPlugin.log(e.getStatus());
}
}
}
}
/**
* 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 {
return AntLaunchingUtil.getCustomClasspath(config);
}
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 Object[] { 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 AntLaunchingUtil.parseRunTargets(extraAttibuteValue);
}
/**
* Returns the list of Strings that were delimiter separated.
*
* @param delimString
* the String to be tokenized based on the delimiter
* @param delim
* the delimiter
* @return a list of Strings
*/
public static String[] parseString(String delimString, String delim) {
return AntLaunchingUtil.parseString(delimString, delim);
}
/**
* Returns an IFile with the given fully qualified path (relative to the workspace root). The returned IFile may or may not exist.
*
* @param fullPath
* the path to look up
* @return the {@link IFile} which may or may not exist
*/
public static IFile getFile(String fullPath) {
return AntLaunchingUtil.getFile(fullPath);
}
public static IHyperlink getLocationLink(String path, File buildFileParent) {
path = path.trim();
if (path.length() == 0) {
return null;
}
if (path.startsWith(IAntCoreConstants.FILE_PROTOCOL)) {
// 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 == -1) {
// incorrect format
return null;
}
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);
try {
String lineNumber = path.substring(index + 1);
int line = Integer.parseInt(lineNumber);
IFile file = getFileForLocation(fileName, buildFileParent);
if (file != null) {
return new FileLink(file, null, -1, -1, line);
}
}
catch (NumberFormatException e) {
// do nothing
}
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) {
return AntLaunchingUtil.getFileForLocation(path, buildFileParent);
}
/**
* Migrates the classpath in the given configuration from the old format to the new format. 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 {
AntLaunchingUtil.migrateToNewClasspathFormat(configuration);
}
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) {
// do nothing
}
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 editorDescriptor
* 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.AntUtil_0, new Object[] { fileResource.getLocation().toOSString() }), e);
}
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, true, true);
}
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(() -> {
IWorkbenchBrowserSupport support = PlatformUI.getWorkbench().getBrowserSupport();
try {
IWebBrowser browser = support.createBrowser(fgBrowserId);
fgBrowserId = browser.getId();
browser.openURL(new URL(urlString));
return;
}
catch (PartInitException e1) {
AntUIPlugin.log(e1.getStatus());
}
catch (MalformedURLException e2) {
AntUIPlugin.log(e2);
}
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.AntUtil_1);
}
});
}
public static boolean isSeparateJREAntBuild(ILaunchConfiguration configuration) {
return AntLaunchingUtil.isSeparateJREAntBuild(configuration);
}
/**
* Returns if the given extension is a known extension to Ant i.e. a supported content type extension.
*
* @param resource
* @return true if the file extension is supported false otherwise
*
* @since 3.6
*/
public static boolean isKnownAntFile(IResource resource) {
if (resource != null) {
// workspace file
IFile file = null;
if (resource.getType() == IResource.FILE) {
file = (IFile) resource;
} else {
file = resource.getAdapter(IFile.class);
}
if (file != null) {
IContentType fileType = IDE.getContentType(file);
if (fileType == null) {
return false;
}
IContentType antType = Platform.getContentTypeManager().getContentType(AntCorePlugin.ANT_BUILDFILE_CONTENT_TYPE);
if (antType != null) {
return fileType.isKindOf(antType);
}
}
}
return false;
}
/**
* Returns if the given extension is a known extension to Ant i.e. a supported content type extension.
*
* @param file
* @return true if the file extension is supported false otherwise
*
* @since 3.8
*/
public static boolean isKnownAntFile(File file) {
if (file != null && !file.isDirectory()) {
String filename = file.getName();
IContentType type = Platform.getContentTypeManager().findContentTypeFor(filename);
if (type != null) {
IContentType antType = Platform.getContentTypeManager().getContentType(AntCorePlugin.ANT_BUILDFILE_CONTENT_TYPE);
if (antType != null) {
return type.isKindOf(antType);
}
}
String[] names = getKnownBuildfileNames();
for (int i = 0; names != null && i < names.length; i++) { // names can be null!
if (filename.endsWith(names[i])) {
return true;
}
}
}
return false;
}
/**
* Returns an array of build file names from the ant preference store
*
* @return an array of build file names
* @since 3.6
*/
public static String[] getKnownBuildfileNames() {
IPreferenceStore prefs = AntUIPlugin.getDefault().getPreferenceStore();
String buildFileNames = prefs.getString(IAntUIPreferenceConstants.ANT_FIND_BUILD_FILE_NAMES);
if (buildFileNames.length() == 0) {
// the user has not specified any names to look for
return null;
}
return parseString(buildFileNames, ","); //$NON-NLS-1$
}
/**
* Returns if the given file is a known build file name, based on the given names from the Ant &gt; Names preference
*
* @param filename
* @return true if the name of the file is given in the Ant &gt; Names preference, false otherwise
* @since 3.6
*/
public static boolean isKnownBuildfileName(String filename) {
String[] names = getKnownBuildfileNames();
for (int i = 0; names != null && i < names.length; i++) { // names can be null!
if (names[i].equalsIgnoreCase(filename)) {
return true;
}
}
return false;
}
/**
* A helper method to extract the build filename extensions as defined in the extender of the content-types extension-point.
*
* @return An empty array or list of filename extensions as specified in the content-types extension
* @since 3.8
*/
public static String[] getKnownBuildFileExtensions() {
IContentType antType = null;
String[] result = null;
try {
antType = Platform.getContentTypeManager().getContentType(AntCorePlugin.ANT_BUILDFILE_CONTENT_TYPE);
if (antType != null) {
result = antType.getFileSpecs(IContentType.FILE_EXTENSION_SPEC);
}
}
catch (Exception e) {
// Empty block and fall-thru is intentional!
}
return result == null ? new String[0] : result;
}
/**
* A helper method to construct a RegEx pattern out of the extensions
*
* @return A String that is a RegEx pattern representing the extensions
* @since 3.8
*/
public static String getKnownBuildFileExtensionsAsPattern() {
String[] extns = AntUtil.getKnownBuildFileExtensions();
if (extns.length == 0) {
return IAntCoreConstants.XML_EXTENSION;
}
StringBuffer sb = new StringBuffer();
for (int i = 0; i < extns.length; i++) {
if (i > 0)
sb.append('|');
sb.append(extns[i]);
}
return sb.toString();
}
/**
* Returns if the given file name is known as a build file. This method consults all of the known file extensions from the Ant-defined content
* types
*
* @param name
* @return <code>true</code> if the file name matches an Ant build file pattern <code>false</code> otherwise
* @since 3.8
*/
public static boolean isKnownAntFileName(String name) {
StringBuffer buf = new StringBuffer(".*.("); //$NON-NLS-1$
buf.append(getKnownBuildFileExtensionsAsPattern());
buf.append(")"); //$NON-NLS-1$
try {
Pattern p = Pattern.compile(buf.toString());
return p.matcher(name).matches();
}
catch (PatternSyntaxException pse) {
return false;
}
}
}