/******************************************************************************* * 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; } }