blob: d9aafff3cb0a07384eab113fafbdac406dc3ffef [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2011, Dariusz Luksza <dariusz@luksza.org>
* Copyright (C) 2011, 2013 Robin Stocker <robin@nibor.org>
* Copyright (C) 2011, Bernard Leach <leachbj@bouncycastle.org>
* Copyright (C) 2013, Michael Keppler <michael.keppler@gmx.de>
* Copyright (C) 2014, IBM Corporation (Markus Keller <markus_keller@ch.ibm.com>)
* Copyright (C) 2015, IBM Corporation (Dani Megert <daniel_megert@ch.ibm.com>)
* Copyright (C) 2015, Thomas Wolf <thomas.wolf@paranor.ch>
* Copyright (C) 2016, Stefan Dirix <sdirix@eclipsesource.com>
*
* All rights reserved. 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
*******************************************************************************/
package org.eclipse.egit.ui.internal;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ParameterizedCommand;
import org.eclipse.core.commands.common.CommandException;
import org.eclipse.core.expressions.EvaluationContext;
import org.eclipse.core.resources.IResource;
import org.eclipse.jface.util.Policy;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.util.StringUtils;
import org.eclipse.ui.ISources;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.services.IServiceLocator;
/**
* Class containing all common utils
*/
public class CommonUtils {
/**
* Pattern to figure out where the footer lines in a commit are.
*
* @see org.eclipse.jgit.revwalk.RevCommit#getFooterLines()
*/
private static final Pattern FOOTER_PATTERN = Pattern
.compile("(?:\n(?:[A-Za-z0-9-]+:[^\n]*))+\\s*$"); //$NON-NLS-1$
private CommonUtils() {
// non-instantiable utility class
}
/**
* Instance of comparator that sorts strings in ascending alphabetical and
* numerous order (also known as natural order), case insensitive.
*
* The comparator is guaranteed to return a non-zero value if
* {@code string1.equals(string2)} returns {@code false}.
*/
public static final Comparator<String> STRING_ASCENDING_COMPARATOR = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
if (o1.length() == 0 || o2.length() == 0)
return o1.length() - o2.length();
LinkedList<String> o1Parts = splitIntoDigitAndNonDigitParts(o1);
LinkedList<String> o2Parts = splitIntoDigitAndNonDigitParts(o2);
Iterator<String> o2PartsIterator = o2Parts.iterator();
for (String o1Part : o1Parts) {
if (!o2PartsIterator.hasNext())
return 1;
String o2Part = o2PartsIterator.next();
int result;
if (Character.isDigit(o1Part.charAt(0)) && Character.isDigit(o2Part.charAt(0))) {
o1Part = stripLeadingZeros(o1Part);
o2Part = stripLeadingZeros(o2Part);
result = o1Part.length() - o2Part.length();
if (result == 0)
result = o1Part.compareToIgnoreCase(o2Part);
} else {
result = o1Part.compareToIgnoreCase(o2Part);
}
if (result != 0)
return result;
}
if (o2PartsIterator.hasNext())
return -1;
else {
// strings are equal (in the Object.equals() sense)
// or only differ in case and/or leading zeros
return o1.compareTo(o2);
}
}
private LinkedList<String> splitIntoDigitAndNonDigitParts(
String input) {
LinkedList<String> parts = new LinkedList<>();
int partStart = 0;
boolean previousWasDigit = Character.isDigit(input.charAt(0));
for (int i = 1; i < input.length(); i++) {
boolean isDigit = Character.isDigit(input.charAt(i));
if (isDigit != previousWasDigit) {
parts.add(input.substring(partStart, i));
partStart = i;
previousWasDigit = isDigit;
}
}
parts.add(input.substring(partStart));
return parts;
}
private String stripLeadingZeros(String input) {
for (int i = 0; i < input.length(); i++) {
if (input.charAt(i) != '0') {
return input.substring(i);
}
}
return ""; //$NON-NLS-1$
}
};
/**
* Instance of comparator which sorts {@link Ref} names using
* {@link CommonUtils#STRING_ASCENDING_COMPARATOR}.
*/
public static final Comparator<Ref> REF_ASCENDING_COMPARATOR = new Comparator<Ref>() {
@Override
public int compare(Ref o1, Ref o2) {
return STRING_ASCENDING_COMPARATOR.compare(o1.getName(), o2.getName());
}
};
/**
* Comparator for comparing {@link IResource} by the result of
* {@link IResource#getName()}.
*/
public static final Comparator<IResource> RESOURCE_NAME_COMPARATOR = new Comparator<IResource>() {
@Override
public int compare(IResource r1, IResource r2) {
return Policy.getComparator().compare(r1.getName(), r2.getName());
}
};
/**
* Comparator for comparing (@link Path} by the result of
* {@link Path#toAbsolutePath()}
*/
public static final Comparator<Path> PATH_STRING_COMPARATOR = new Comparator<Path>() {
@Override
public int compare(Path p1, Path p2) {
return STRING_ASCENDING_COMPARATOR.compare(
p1.toAbsolutePath().toString(),
p2.toAbsolutePath().toString());
}
};
/**
* Programmatically run command based on its id and given selection
*
* @param commandId
* id of command that should be run
* @param selection
* given selection
* @return {@code true} when command was successfully executed,
* {@code false} otherwise
*/
public static boolean runCommand(String commandId,
IStructuredSelection selection) {
ICommandService commandService = CommonUtils.getService(PlatformUI
.getWorkbench(), ICommandService.class);
Command cmd = commandService.getCommand(commandId);
if (!cmd.isDefined())
return false;
IHandlerService handlerService = CommonUtils.getService(PlatformUI
.getWorkbench(), IHandlerService.class);
EvaluationContext c = null;
if (selection != null) {
c = new EvaluationContext(
handlerService.createContextSnapshot(false),
selection.toList());
c.addVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME, selection);
c.removeVariable(ISources.ACTIVE_MENU_SELECTION_NAME);
}
try {
if (c != null)
handlerService.executeCommandInContext(
new ParameterizedCommand(cmd, null), null, c);
else
handlerService.executeCommand(commandId, null);
return true;
} catch (CommandException ignored) {
// Ignored
}
return false;
}
/**
* Retrieves the service corresponding to the given API.
* <p>
* Workaround for "Unnecessary cast" errors, see bug 441615. Can be removed
* when EGit depends on Eclipse 4.5 or higher.
*
* @param locator
* the service locator, must not be null
* @param api
* the interface the service implements, must not be null
* @return the service, or null if no such service could be found
* @see IServiceLocator#getService(Class)
*/
@SuppressWarnings("unchecked")
public static <T> T getService(IServiceLocator locator, Class<T> api) {
Object service = locator.getService(api);
return (T) service;
}
/**
* Assuming that the string {@code commitMessage} is a commit message,
* returns the offset in the string of the footer of the commit message, if
* one can be found, or -1 otherwise.
* <p>
* A footer of a commit message is defined to be the non-empty lines
* following the last empty line in the commit message if they have the
* format "key: value" as defined by
* {@link org.eclipse.jgit.revwalk.RevCommit#getFooterLines()}, like
* Change-Id: I000... or Signed-off-by: ... Empty lines at the end of the
* commit message are ignored.
* </p>
*
* @param commitMessage
* text of the commit message, assumed to use '\n' as line
* delimiter
* @return the index of the beginning of the footer, if any, or -1
* otherwise.
*/
public static int getFooterOffset(String commitMessage) {
if (commitMessage == null) {
return -1;
}
Matcher matcher = FOOTER_PATTERN.matcher(commitMessage);
if (matcher.find()) {
int start = matcher.start();
// Check that the line that ends at start is empty.
int i = start - 1;
while (i >= 0) {
char ch = commitMessage.charAt(i--);
if (ch == '\n') {
return start + 1;
} else if (!Character.isWhitespace(ch)) {
return -1;
}
}
// No \n but only whitespace: first line is empty
return start + 1;
}
return -1;
}
/**
* Creates a comma separated list of all non-null resource names. The last
* element is separated with an ampersand.
*
* @param resources
* the collection of {@link IResource}s.
* @return A comma separated list of the resource names. The last element is
* separated with an ampersand.
*/
public static String getResourceNames(Iterable<IResource> resources) {
final List<String> names = new LinkedList<>();
for (IResource resource : resources) {
if (resource.getName() != null) {
names.add(resource.getName());
}
}
return StringUtils.join(names, ", ", " & "); //$NON-NLS-1$ //$NON-NLS-2$
}
}