/*******************************************************************************
* Copyright (c) 2000, 2011 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.ganoro.phing.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 org.apache.tools.ant.Target;
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.externaltools.internal.model.IExternalToolConstants;
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 org.ganoro.phing.core.AntCoreUtil;
import org.ganoro.phing.core.IPhingCoreConstants;
import org.ganoro.phing.core.PhingCore;
import org.ganoro.phing.launching.IAntLaunchConstants;
import org.ganoro.phing.launching.internal.AntLaunchingUtil;
import org.ganoro.phing.ui.editors.PhingEditor;
import org.ganoro.phing.ui.internal.model.AntElementNode;
import org.ganoro.phing.ui.internal.model.AntModel;
import org.ganoro.phing.ui.internal.model.AntProjectNode;
import org.ganoro.phing.ui.internal.model.AntTargetNode;
import org.ganoro.phing.ui.internal.model.IAntModel;
import org.ganoro.phing.ui.internal.model.LocationProvider;
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 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 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, "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) {
}
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 allArgs = config.getAttribute(
IExternalToolConstants.ATTR_TOOL_ARGUMENTS, (String) null);
Map properties = new HashMap();
if (allArgs != null) {
String[] arguments = ExternalToolsUtil.parseStringIntoList(allArgs);
// filter arguments to avoid resolving variables that will prompt
// the user
List 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 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;
}
AntTargetNode[] targets = getTargets(project);
if (targets == null) {
Hashtable antTargets = project.getProject().getTargets();
Target implicitTarget = (Target) antTargets
.get(IPhingCoreConstants.EMPTY_STRING);
if (implicitTarget != null) {
AntTargetNode implicitTargetNode = new AntTargetNode(
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()
*/
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(PhingCore.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) {
PhingUi.log(ce.getStatus());
return null;
} finally {
if (connected) {
try {
manager.disconnect(location, LocationKind.NORMALIZE,
new NullProgressMonitor());
} catch (CoreException e) {
PhingUi.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 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 AntLaunchingUtil.parseRunTargets(extraAttibuteValue);
}
/**
* 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) {
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.
*/
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(IPhingCoreConstants.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) {
}
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);
}
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 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) {
PhingUi.log(MessageFormat.format("AntUtil_0",
new String[] { fileResource.getLocation().toOSString() }),
e);
}
if (editorPart instanceof PhingEditor) {
PhingEditor editor = (PhingEditor) 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() {
IWorkbenchBrowserSupport support = PlatformUI.getWorkbench()
.getBrowserSupport();
try {
IWebBrowser browser = support.createBrowser(fgBrowserId);
fgBrowserId = browser.getId();
browser.openURL(new URL(urlString));
return;
} catch (PartInitException e) {
PhingUi.log(e.getStatus());
} catch (MalformedURLException e) {
PhingUi.log(e);
}
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,
"AntUtil_1");
}
}
});
}
/**
* 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) {
IFile file = null;
if (resource.getType() == IResource.FILE) {
file = (IFile) resource;
} else {
file = (IFile) resource.getAdapter(IFile.class);
}
if (file != null) {
IContentType fileType = IDE.getContentType(file);
IContentType antType = Platform.getContentTypeManager()
.getContentType(PhingCore.ANT_BUILDFILE_CONTENT_TYPE);
return fileType.isKindOf(antType);
}
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 = PhingUi.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 > Names preference
*
* @param filename
* @return true if the name of the file is given in the Ant > Names
* preference, false otherwise
* @since 3.6
*/
public static boolean isKnownBuildfileName(String filename) {
String[] names = getKnownBuildfileNames();
for (int i = 0; i < names.length; i++) {
if (names[i].equalsIgnoreCase(filename)) {
return true;
}
}
return false;
}
}