Update org.apache.commons:commons-compress to 1.25.0
[egit/eclipse.git] / org.eclipse.egit.ui / src / org / eclipse / egit / ui / internal / CommonUtils.java
blobae2395f9aa6d27b0b9002f73239039313510eab9
1 /*******************************************************************************
2 * Copyright (C) 2011, 2020 Dariusz Luksza <dariusz@luksza.org> and others.
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) 2016, Stefan Dirix <sdirix@eclipsesource.com>
10 * All rights reserved. This program and the accompanying materials
11 * are made available under the terms of the Eclipse Public License 2.0
12 * which accompanies this distribution, and is available at
13 * https://www.eclipse.org/legal/epl-2.0/
15 * SPDX-License-Identifier: EPL-2.0
16 *******************************************************************************/
17 package org.eclipse.egit.ui.internal;
19 import java.nio.file.Path;
20 import java.text.MessageFormat;
21 import java.util.Comparator;
22 import java.util.Iterator;
23 import java.util.LinkedList;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
29 import org.eclipse.core.commands.Command;
30 import org.eclipse.core.commands.ParameterizedCommand;
31 import org.eclipse.core.commands.common.CommandException;
32 import org.eclipse.core.expressions.EvaluationContext;
33 import org.eclipse.core.resources.IResource;
34 import org.eclipse.egit.ui.Activator;
35 import org.eclipse.jface.util.Policy;
36 import org.eclipse.jface.viewers.IStructuredSelection;
37 import org.eclipse.jgit.lib.Ref;
38 import org.eclipse.jgit.util.StringUtils;
39 import org.eclipse.swt.graphics.GC;
40 import org.eclipse.swt.widgets.Control;
41 import org.eclipse.swt.widgets.Table;
42 import org.eclipse.ui.ISources;
43 import org.eclipse.ui.PlatformUI;
44 import org.eclipse.ui.commands.ICommandService;
45 import org.eclipse.ui.handlers.IHandlerService;
47 /**
48 * Class containing all common utils
50 public class CommonUtils {
52 /**
53 * Pattern to figure out where the footer lines in a commit are.
55 * @see org.eclipse.jgit.revwalk.RevCommit#getFooterLines()
57 private static final Pattern FOOTER_PATTERN = Pattern
58 .compile("(?:\n(?:[A-Za-z0-9-]+:[^\n]*))+\\s*$"); //$NON-NLS-1$
60 /**
61 * Minimum inset to draw table column text without shortening on Windows.
62 * The code of {@link Table} suggests 4, but that lead to shortening of
63 * text.
65 private static final int TABLE_INSET = 5;
67 private CommonUtils() {
68 // non-instantiable utility class
71 /**
72 * Instance of comparator that sorts strings in ascending alphabetical and
73 * numerous order (also known as natural order), case insensitive.
75 * The comparator is guaranteed to return a non-zero value if
76 * {@code string1.equals(string2)} returns {@code false}.
78 public static final Comparator<String> STRING_ASCENDING_COMPARATOR = new Comparator<>() {
79 @Override
80 public int compare(String o1, String o2) {
81 if (o1.length() == 0 || o2.length() == 0)
82 return o1.length() - o2.length();
84 LinkedList<String> o1Parts = splitIntoDigitAndNonDigitParts(o1);
85 LinkedList<String> o2Parts = splitIntoDigitAndNonDigitParts(o2);
87 Iterator<String> o2PartsIterator = o2Parts.iterator();
89 for (String o1Part : o1Parts) {
90 if (!o2PartsIterator.hasNext())
91 return 1;
93 String o2Part = o2PartsIterator.next();
95 int result;
97 if (Character.isDigit(o1Part.charAt(0))
98 && Character.isDigit(o2Part.charAt(0))) {
99 o1Part = stripLeadingZeros(o1Part);
100 o2Part = stripLeadingZeros(o2Part);
101 result = o1Part.length() - o2Part.length();
102 if (result == 0)
103 result = o1Part.compareToIgnoreCase(o2Part);
104 } else {
105 result = o1Part.compareToIgnoreCase(o2Part);
108 if (result != 0)
109 return result;
112 if (o2PartsIterator.hasNext())
113 return -1;
114 else {
115 // strings are equal (in the Object.equals() sense)
116 // or only differ in case and/or leading zeros
117 return o1.compareTo(o2);
121 private LinkedList<String> splitIntoDigitAndNonDigitParts(
122 String input) {
123 LinkedList<String> parts = new LinkedList<>();
124 int partStart = 0;
125 boolean previousWasDigit = Character.isDigit(input.charAt(0));
126 for (int i = 1; i < input.length(); i++) {
127 boolean isDigit = Character.isDigit(input.charAt(i));
128 if (isDigit != previousWasDigit) {
129 parts.add(input.substring(partStart, i));
130 partStart = i;
131 previousWasDigit = isDigit;
134 parts.add(input.substring(partStart));
135 return parts;
138 private String stripLeadingZeros(String input) {
139 for (int i = 0; i < input.length(); i++) {
140 if (input.charAt(i) != '0') {
141 return input.substring(i);
144 return ""; //$NON-NLS-1$
149 * Instance of comparator which sorts {@link Ref} names using
150 * {@link CommonUtils#STRING_ASCENDING_COMPARATOR}.
152 public static final Comparator<Ref> REF_ASCENDING_COMPARATOR = Comparator
153 .comparing(Ref::getName, STRING_ASCENDING_COMPARATOR);
156 * Comparator for comparing {@link IResource} by the result of
157 * {@link IResource#getName()}.
159 public static final Comparator<IResource> RESOURCE_NAME_COMPARATOR = //
160 (a, b) -> Policy.getComparator().compare(a.getName(), b.getName());
163 * Comparator for comparing (@link Path} by the result of
164 * {@link Path#toAbsolutePath()}
166 public static final Comparator<Path> PATH_STRING_COMPARATOR = Comparator
167 .comparing(p -> p.toAbsolutePath().toString(),
168 STRING_ASCENDING_COMPARATOR);
171 * Programmatically run a command based on its id and given selection.
173 * @param commandId
174 * id of command that should be run
175 * @param selection
176 * given selection
177 * @return {@code true} when command was successfully executed,
178 * {@code false} otherwise
180 public static boolean runCommand(String commandId,
181 IStructuredSelection selection) {
182 return runCommand(commandId, selection, null);
186 * Programmatically run a command based on its id and given selection,
187 * optionally passing parameters to the command.
189 * @param commandId
190 * id of command that should be run
191 * @param selection
192 * given selection
193 * @param params
194 * optional command parameters to apply, may be {@code null}
195 * @return {@code true} when command was successfully executed,
196 * {@code false} otherwise
198 public static boolean runCommand(String commandId,
199 IStructuredSelection selection, Map<String, Object> params) {
200 ICommandService commandService = PlatformUI.getWorkbench()
201 .getService(ICommandService.class);
202 Command cmd = commandService.getCommand(commandId);
203 if (!cmd.isDefined()) {
204 return false;
206 IHandlerService handlerService = PlatformUI.getWorkbench()
207 .getService(IHandlerService.class);
208 EvaluationContext c = null;
209 if (selection != null) {
210 c = new EvaluationContext(
211 handlerService.createContextSnapshot(false),
212 selection.toList());
213 c.addVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME, selection);
214 c.removeVariable(ISources.ACTIVE_MENU_SELECTION_NAME);
216 try {
217 if (c != null) {
218 handlerService.executeCommandInContext(
219 ParameterizedCommand.generateCommand(cmd, params), null,
221 } else {
222 handlerService.executeCommand(
223 ParameterizedCommand.generateCommand(cmd, params),
224 null);
226 return true;
227 } catch (CommandException e) {
228 Activator.logError(MessageFormat
229 .format(UIText.CommonUtils_CommandError, commandId), e);
231 return false;
235 * Assuming that the string {@code commitMessage} is a commit message,
236 * returns the offset in the string of the footer of the commit message, if
237 * one can be found, or -1 otherwise.
238 * <p>
239 * A footer of a commit message is defined to be the non-empty lines
240 * following the last empty line in the commit message if they have the
241 * format "key: value" as defined by
242 * {@link org.eclipse.jgit.revwalk.RevCommit#getFooterLines()}, like
243 * Change-Id: I000... or Signed-off-by: ... Empty lines at the end of the
244 * commit message are ignored.
245 * </p>
247 * @param commitMessage
248 * text of the commit message, assumed to use '\n' as line
249 * delimiter
250 * @return the index of the beginning of the footer, if any, or -1
251 * otherwise.
253 public static int getFooterOffset(String commitMessage) {
254 if (commitMessage == null) {
255 return -1;
257 Matcher matcher = FOOTER_PATTERN.matcher(commitMessage);
258 if (matcher.find()) {
259 int start = matcher.start();
260 // Check that the line that ends at start is empty.
261 int i = start - 1;
262 while (i >= 0) {
263 char ch = commitMessage.charAt(i--);
264 if (ch == '\n') {
265 return start + 1;
266 } else if (!Character.isWhitespace(ch)) {
267 return -1;
270 // No \n but only whitespace: first line is empty
271 return start + 1;
273 return -1;
277 * Creates a comma separated list of all non-null resource names. The last
278 * element is separated with an ampersand.
280 * @param resources
281 * the collection of {@link IResource}s.
282 * @return A comma separated list of the resource names. The last element is
283 * separated with an ampersand.
285 public static String getResourceNames(Iterable<IResource> resources) {
286 final List<String> names = new LinkedList<>();
287 for (IResource resource : resources) {
288 if (resource.getName() != null) {
289 names.add(resource.getName());
293 return StringUtils.join(names, ", ", " & "); //$NON-NLS-1$ //$NON-NLS-2$
297 * @param control
298 * SWT table
299 * @param columnHeading
300 * column heading
301 * @return column width for a table column of the given table to fit a git
302 * commit SHA1
304 public static int getCommitIdColumnWidth(Control control,
305 String columnHeading) {
306 GC gc = new GC(control.getDisplay());
307 try {
308 gc.setFont(control.getFont());
309 return Math.max(gc.stringExtent("bbbbbbb").x, //$NON-NLS-1$
310 gc.stringExtent(columnHeading).x) + 2 * TABLE_INSET;
311 } finally {
312 gc.dispose();