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
;
44 * Class containing all common utils
46 public class CommonUtils
{
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
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
>() {
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())
82 String o2Part
= o2PartsIterator
.next();
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();
91 result
= o1Part
.compareToIgnoreCase(o2Part
);
93 result
= o1Part
.compareToIgnoreCase(o2Part
);
100 if (o2PartsIterator
.hasNext())
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(
111 LinkedList
<String
> parts
= new LinkedList
<>();
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
));
119 previousWasDigit
= isDigit
;
122 parts
.add(input
.substring(partStart
));
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
>() {
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
>() {
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
>() {
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
175 * id of command that should be run
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())
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),
196 c
.addVariable(ISources
.ACTIVE_CURRENT_SELECTION_NAME
, selection
);
197 c
.removeVariable(ISources
.ACTIVE_MENU_SELECTION_NAME
);
201 handlerService
.executeCommandInContext(
202 new ParameterizedCommand(cmd
, null), null, c
);
204 handlerService
.executeCommand(commandId
, null);
207 } catch (CommandException ignored
) {
214 * Retrieves the service corresponding to the given API.
216 * Workaround for "Unnecessary cast" errors, see bug 441615. Can be removed
217 * when EGit depends on Eclipse 4.5 or higher.
220 * the service locator, must not be null
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
);
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.
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.
245 * @param commitMessage
246 * text of the commit message, assumed to use '\n' as line
248 * @return the index of the beginning of the footer, if any, or -1
251 public static int getFooterOffset(String commitMessage
) {
252 if (commitMessage
== null) {
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.
261 char ch
= commitMessage
.charAt(i
--);
264 } else if (!Character
.isWhitespace(ch
)) {
268 // No \n but only whitespace: first line is empty
275 * Creates a comma separated list of all non-null resource names. The last
276 * element is separated with an ampersand.
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$