StagingView wrongly sorted by state initially
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / CommonUtils.java
blob759264e720cfa18ec34c2452bd2d6a13068e087a
1 /*******************************************************************************
2 * Copyright (C) 2011, Dariusz Luksza <dariusz@luksza.org>
3 * Copyright (C) 2011, 2013 Robin Stocker <robin@nibor.org>
4 * Copyright (C) 2011, Bernard Leach <leachbj@bouncycastle.org>
5 * Copyright (C) 2013, Michael Keppler <michael.keppler@gmx.de>
6 * Copyright (C) 2014, IBM Corporation (Markus Keller <markus_keller@ch.ibm.com>)
7 * Copyright (C) 2015, IBM Corporation (Dani Megert <daniel_megert@ch.ibm.com>)
8 * Copyright (C) 2015, Thomas Wolf <thomas.wolf@paranor.ch>
9 * Copyright (C) 2016, Stefan Dirix <sdirix@eclipsesource.com>
11 * All rights reserved. This program and the accompanying materials
12 * are made available under the terms of the Eclipse Public License 2.0
13 * which accompanies this distribution, and is available at
14 * https://www.eclipse.org/legal/epl-2.0/
16 * SPDX-License-Identifier: EPL-2.0
17 *******************************************************************************/
18 package org.eclipse.egit.ui.internal;
20 import java.nio.file.Path;
21 import java.util.Comparator;
22 import java.util.Iterator;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
28 import org.eclipse.core.commands.Command;
29 import org.eclipse.core.commands.ParameterizedCommand;
30 import org.eclipse.core.commands.common.CommandException;
31 import org.eclipse.core.expressions.EvaluationContext;
32 import org.eclipse.core.resources.IResource;
33 import org.eclipse.jface.util.Policy;
34 import org.eclipse.jface.viewers.IStructuredSelection;
35 import org.eclipse.jgit.lib.Ref;
36 import org.eclipse.jgit.util.StringUtils;
37 import org.eclipse.ui.ISources;
38 import org.eclipse.ui.PlatformUI;
39 import org.eclipse.ui.commands.ICommandService;
40 import org.eclipse.ui.handlers.IHandlerService;
41 import org.eclipse.ui.services.IServiceLocator;
43 /**
44 * Class containing all common utils
46 public class CommonUtils {
48 /**
49 * Pattern to figure out where the footer lines in a commit are.
51 * @see org.eclipse.jgit.revwalk.RevCommit#getFooterLines()
53 private static final Pattern FOOTER_PATTERN = Pattern
54 .compile("(?:\n(?:[A-Za-z0-9-]+:[^\n]*))+\\s*$"); //$NON-NLS-1$
56 private CommonUtils() {
57 // non-instantiable utility class
60 /**
61 * Instance of comparator that sorts strings in ascending alphabetical and
62 * numerous order (also known as natural order), case insensitive.
64 * The comparator is guaranteed to return a non-zero value if
65 * string1.equals(String2) returns false
67 public static final Comparator<String> STRING_ASCENDING_COMPARATOR = new Comparator<String>() {
68 @Override
69 public int compare(String o1, String o2) {
70 if (o1.length() == 0 || o2.length() == 0)
71 return o1.length() - o2.length();
73 LinkedList<String> o1Parts = splitIntoDigitAndNonDigitParts(o1);
74 LinkedList<String> o2Parts = splitIntoDigitAndNonDigitParts(o2);
76 Iterator<String> o2PartsIterator = o2Parts.iterator();
78 for (String o1Part : o1Parts) {
79 if (!o2PartsIterator.hasNext())
80 return 1;
82 String o2Part = o2PartsIterator.next();
84 int result;
86 if (Character.isDigit(o1Part.charAt(0)) && Character.isDigit(o2Part.charAt(0))) {
87 o1Part = stripLeadingZeros(o1Part);
88 o2Part = stripLeadingZeros(o2Part);
89 result = o1Part.length() - o2Part.length();
90 if (result == 0)
91 result = o1Part.compareToIgnoreCase(o2Part);
92 } else {
93 result = o1Part.compareToIgnoreCase(o2Part);
96 if (result != 0)
97 return result;
100 if (o2PartsIterator.hasNext())
101 return -1;
102 else {
103 // strings are equal (in the Object.equals() sense)
104 // or only differ in case and/or leading zeros
105 return o1.compareTo(o2);
109 private LinkedList<String> splitIntoDigitAndNonDigitParts(
110 String input) {
111 LinkedList<String> parts = new LinkedList<>();
112 int partStart = 0;
113 boolean previousWasDigit = Character.isDigit(input.charAt(0));
114 for (int i = 1; i < input.length(); i++) {
115 boolean isDigit = Character.isDigit(input.charAt(i));
116 if (isDigit != previousWasDigit) {
117 parts.add(input.substring(partStart, i));
118 partStart = i;
119 previousWasDigit = isDigit;
122 parts.add(input.substring(partStart));
123 return parts;
126 private String stripLeadingZeros(String input) {
127 for (int i = 0; i < input.length(); i++) {
128 if (input.charAt(i) != '0') {
129 return input.substring(i);
132 return ""; //$NON-NLS-1$
137 * Instance of comparator which sorts {@link Ref} names using
138 * {@link CommonUtils#STRING_ASCENDING_COMPARATOR}.
140 public static final Comparator<Ref> REF_ASCENDING_COMPARATOR = new Comparator<Ref>() {
141 @Override
142 public int compare(Ref o1, Ref o2) {
143 return STRING_ASCENDING_COMPARATOR.compare(o1.getName(), o2.getName());
148 * Comparator for comparing {@link IResource} by the result of
149 * {@link IResource#getName()}.
151 public static final Comparator<IResource> RESOURCE_NAME_COMPARATOR = new Comparator<IResource>() {
152 @Override
153 public int compare(IResource r1, IResource r2) {
154 return Policy.getComparator().compare(r1.getName(), r2.getName());
159 * Comparator for comparing (@link Path} by the result of
160 * {@link Path#toAbsolutePath()}
162 public static final Comparator<Path> PATH_STRING_COMPARATOR = new Comparator<Path>() {
163 @Override
164 public int compare(Path p1, Path p2) {
165 return STRING_ASCENDING_COMPARATOR.compare(
166 p1.toAbsolutePath().toString(),
167 p2.toAbsolutePath().toString());
172 * Programmatically run command based on its id and given selection
174 * @param commandId
175 * id of command that should be run
176 * @param selection
177 * given selection
178 * @return {@code true} when command was successfully executed,
179 * {@code false} otherwise
181 public static boolean runCommand(String commandId,
182 IStructuredSelection selection) {
183 ICommandService commandService = CommonUtils.getService(PlatformUI
184 .getWorkbench(), ICommandService.class);
185 Command cmd = commandService.getCommand(commandId);
186 if (!cmd.isDefined())
187 return false;
189 IHandlerService handlerService = CommonUtils.getService(PlatformUI
190 .getWorkbench(), IHandlerService.class);
191 EvaluationContext c = null;
192 if (selection != null) {
193 c = new EvaluationContext(
194 handlerService.createContextSnapshot(false),
195 selection.toList());
196 c.addVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME, selection);
197 c.removeVariable(ISources.ACTIVE_MENU_SELECTION_NAME);
199 try {
200 if (c != null)
201 handlerService.executeCommandInContext(
202 new ParameterizedCommand(cmd, null), null, c);
203 else
204 handlerService.executeCommand(commandId, null);
206 return true;
207 } catch (CommandException ignored) {
208 // Ignored
210 return false;
214 * Retrieves the service corresponding to the given API.
215 * <p>
216 * Workaround for "Unnecessary cast" errors, see bug 441615. Can be removed
217 * when EGit depends on Eclipse 4.5 or higher.
219 * @param locator
220 * the service locator, must not be null
221 * @param api
222 * the interface the service implements, must not be null
223 * @return the service, or null if no such service could be found
224 * @see IServiceLocator#getService(Class)
226 @SuppressWarnings("unchecked")
227 public static <T> T getService(IServiceLocator locator, Class<T> api) {
228 Object service = locator.getService(api);
229 return (T) service;
233 * Assuming that the string {@code commitMessage} is a commit message,
234 * returns the offset in the string of the footer of the commit message, if
235 * one can found, or -1 otherwise.
236 * <p>
237 * A footer of a commit message is defined to be the non-empty lines
238 * following the last empty line in the commit message if they have the
239 * format "key: value" as defined by
240 * {@link org.eclipse.jgit.revwalk.RevCommit#getFooterLines()}, like
241 * Change-Id: I000... or Signed-off-by: ... Empty lines at the end of the
242 * commit message are ignored.
243 * </p>
245 * @param commitMessage
246 * text of the commit message, assumed to use '\n' as line
247 * delimiter
248 * @return the index of the beginning of the footer, if any, or -1
249 * otherwise.
251 public static int getFooterOffset(String commitMessage) {
252 if (commitMessage == null) {
253 return -1;
255 Matcher matcher = FOOTER_PATTERN.matcher(commitMessage);
256 if (matcher.find()) {
257 int start = matcher.start();
258 // Check that the line that ends at start is empty.
259 int i = start - 1;
260 while (i >= 0) {
261 char ch = commitMessage.charAt(i--);
262 if (ch == '\n') {
263 return start + 1;
264 } else if (!Character.isWhitespace(ch)) {
265 return -1;
268 // No \n but only whitespace: first line is empty
269 return start + 1;
271 return -1;
275 * Creates a comma separated list of all non-null resource names. The last
276 * element is separated with an ampersand.
278 * @param resources
279 * the collection of {@link IResource}s.
280 * @return A comma separated list the resource names. The last element is
281 * separated with an ampersand.
283 public static String getResourceNames(Iterable<IResource> resources) {
284 final List<String> names = new LinkedList<>();
285 for (IResource resource : resources) {
286 if (resource.getName() != null) {
287 names.add(resource.getName());
291 return StringUtils.join(names, ", ", " & "); //$NON-NLS-1$ //$NON-NLS-2$