blob: 6aba3664de944ad5d8fcaebeff16e61f52b8798d [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2016, 2019 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ltk.buildpaths.core;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
public class BuildpathsUtils {
public static final boolean equalPattern(final IPath pattern1, final IPath pattern2) {
return (pattern1 == pattern2)
|| (pattern1 != null && pattern1.equals(pattern2)
&& pattern1.hasTrailingSeparator() == pattern2.hasTrailingSeparator() );
}
public static boolean equalPatterns(final List<IPath> patterns1, final List<IPath> patterns2) {
if (patterns1 == patterns2) {
return true;
}
if (patterns1 != null && patterns2 != null) {
final int length= patterns1.size();
if (patterns2.size() == length) {
for (int i= 0; i < length; i++) {
if (!equalPattern(patterns1.get(i), patterns2.get(i))) {
return false;
}
}
return true;
}
}
return false;
}
public static ImList<IPath> decodePatterns(final String sequence) {
if (sequence != null) {
if (sequence.isEmpty()) {
return ImCollections.emptyList();
}
final String[] patterns= sequence.split("\\|");
final IPath[] paths= new IPath[patterns.length];
int index= 0;
for (int j= 0; j < patterns.length; j++) {
final String pattern= patterns[j];
if (pattern.isEmpty()) {
continue; // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=105581
}
paths[index++]= new Path(new String(pattern));
}
return ImCollections.newList(paths, 0, index);
}
return null;
}
public static String encodePatterns(final ImList<IPath> patterns) {
if (patterns != null) {
if (patterns.isEmpty()) {
return ""; //$NON-NLS-1$
}
if (patterns.size() == 1) {
return patterns.get(0).toPortableString();
}
final Iterator<IPath> iter= patterns.iterator();
final StringBuilder sb= new StringBuilder(iter.next().toPortableString());
while (iter.hasNext()) {
sb.append('|');
sb.append(iter.next().toPortableString());
}
return sb.toString();
}
return null;
}
public final static boolean isExcluded(final IResource resource,
final IBuildpathElement element) {
final IPath path= resource.getFullPath();
final int resourceType= resource.getType();
final ImList<String> inclusionPatterns= ((BuildpathElement) element).getFullInclusionPatterns();
final ImList<String> exclusionPatterns= ((BuildpathElement) element).getFullExclusionPatterns();
return isExcluded(path.toString(), (resourceType == IResource.FOLDER || resourceType == IResource.PROJECT),
inclusionPatterns, exclusionPatterns );
}
public final static boolean isExcluded(String path, final boolean isFolderPath,
final ImList<String> inclusionPatterns, final ImList<String> exclusionPatterns) {
if (inclusionPatterns == null && exclusionPatterns == null) {
return false;
}
CHECK_INCLUSION: if (inclusionPatterns != null) {
for (final String pattern : exclusionPatterns) {
String folderPattern= pattern;
if (isFolderPath) {
final int lastSlash= pattern.lastIndexOf('/');
if (lastSlash != -1 && lastSlash != pattern.length() - 1) { // trailing slash -> adds '**' for free (see http://ant.apache.org/manual/dirtasks.html)
final int star= pattern.indexOf('*', lastSlash);
if ((star == -1
|| star >= pattern.length() - 1
|| pattern.charAt(star + 1) != '*')) {
folderPattern= pattern.substring(0, lastSlash);
}
}
}
if (matchPath(folderPattern, path, true, '/')) {
break CHECK_INCLUSION;
}
}
return true; // never included
}
if (isFolderPath) {
path= path + "/*";
}
if (exclusionPatterns != null) {
for (final String pattern : exclusionPatterns) {
if (matchPath(pattern, path, true, '/')) {
return true;
}
}
}
return false;
}
/**
* Answers true if the pattern matches the filepath using the pathSepatator, false otherwise.
*
* Path char[] pattern matching, accepting wild-cards '**', '*' and '?' (using Ant directory tasks
* conventions, also see "http://jakarta.apache.org/ant/manual/dirtasks.html#defaultexcludes").
* Path pattern matching is enhancing regular pattern matching in supporting extra rule where '**' represent
* any folder combination.
* Special rule:
* - foo\ is equivalent to foo\**
* When not case sensitive, the pattern is assumed to already be lowercased, the
* name will be lowercased character per character as comparing.
*
* @param pattern the given pattern
* @param filepath the given path
* @param isCaseSensitive to find out whether or not the matching should be case sensitive
* @param pathSeparator the given path separator
* @return true if the pattern matches the filepath using the pathSepatator, false otherwise
*/
public static final boolean matchPath(final String pattern, final String filepath,
final boolean isCaseSensitive, final char pathSeparator) {
if (filepath == null) {
return false; // null name cannot match
}
if (pattern == null) {
return true; // null pattern is equivalent to '*'
}
// offsets inside pattern
int pSegmentStart= (pattern.charAt(0) == pathSeparator) ? 1 : 0;
final int pLength= pattern.length();
int pSegmentEnd= pattern.indexOf(pathSeparator, pSegmentStart + 1);
if (pSegmentEnd < 0) {
pSegmentEnd= pLength;
}
// special case: pattern foo\ is equivalent to foo\**
final boolean freeTrailingDoubleStar= (pattern.charAt(pLength - 1) == pathSeparator);
// offsets inside filepath
int fSegmentStart;
final int fLength= filepath.length();
if (filepath.charAt(0) != pathSeparator){
fSegmentStart= 0;
}
else {
fSegmentStart= 1;
}
if (fSegmentStart != pSegmentStart) {
return false; // both must start with a separator or none.
}
int fSegmentEnd= filepath.indexOf(pathSeparator, fSegmentStart + 1);
if (fSegmentEnd < 0) {
fSegmentEnd= fLength;
}
// first segments
while (pSegmentStart < pLength
&& !(pSegmentEnd == pLength && freeTrailingDoubleStar
|| (pSegmentEnd == pSegmentStart + 2
&& pattern.charAt(pSegmentStart) == '*'
&& pattern.charAt(pSegmentStart + 1) == '*' ))) {
if (fSegmentStart >= fLength) {
return false;
}
if (!match(pattern, pSegmentStart, pSegmentEnd,
filepath, fSegmentStart, fSegmentEnd,
isCaseSensitive )) {
return false;
}
// jump to next segment
pSegmentEnd= pattern.indexOf(pathSeparator, pSegmentStart= pSegmentEnd + 1);
// skip separator
if (pSegmentEnd < 0) {
pSegmentEnd= pLength;
}
fSegmentEnd= filepath.indexOf(pathSeparator, fSegmentStart= fSegmentEnd + 1);
// skip separator
if (fSegmentEnd < 0) {
fSegmentEnd= fLength;
}
}
/* check sequence of doubleStar+segment */
int pSegmentRestart;
if ((pSegmentStart >= pLength && freeTrailingDoubleStar)
|| (pSegmentEnd == pSegmentStart + 2
&& pattern.charAt(pSegmentStart) == '*'
&& pattern.charAt(pSegmentStart + 1) == '*') ) {
pSegmentEnd= pattern.indexOf(pathSeparator, pSegmentStart= pSegmentEnd + 1);
// skip separator
if (pSegmentEnd < 0) {
pSegmentEnd= pLength;
}
pSegmentRestart= pSegmentStart;
}
else {
if (pSegmentStart >= pLength) {
return fSegmentStart >= fLength; // true if filepath is done too.
}
pSegmentRestart= 0; // force fSegmentStart check
}
int fSegmentRestart= fSegmentStart;
CHECK_SEGMENT : while (fSegmentStart < fLength) {
if (pSegmentStart >= pLength) {
if (freeTrailingDoubleStar) {
return true;
}
// mismatch - restart current path segment
pSegmentEnd= pattern.indexOf(pathSeparator, pSegmentStart= pSegmentRestart);
if (pSegmentEnd < 0) {
pSegmentEnd= pLength;
}
fSegmentRestart= filepath.indexOf(pathSeparator, fSegmentRestart + 1);
// skip separator
if (fSegmentRestart < 0) {
fSegmentRestart= fLength;
}
else {
fSegmentRestart++;
}
fSegmentEnd= filepath.indexOf(pathSeparator, fSegmentStart= fSegmentRestart);
if (fSegmentEnd < 0) {
fSegmentEnd= fLength;
}
continue CHECK_SEGMENT;
}
/* path segment is ending */
if (pSegmentEnd == pSegmentStart + 2
&& pattern.charAt(pSegmentStart) == '*'
&& pattern.charAt(pSegmentStart + 1) == '*') {
pSegmentEnd= pattern.indexOf(pathSeparator, pSegmentStart= pSegmentEnd + 1);
// skip separator
if (pSegmentEnd < 0) {
pSegmentEnd= pLength;
}
pSegmentRestart= pSegmentStart;
fSegmentRestart= fSegmentStart;
if (pSegmentStart >= pLength) {
return true;
}
continue CHECK_SEGMENT;
}
/* chech current path segment */
if (!match(pattern, pSegmentStart, pSegmentEnd,
filepath, fSegmentStart, fSegmentEnd,
isCaseSensitive )) {
// mismatch - restart current path segment
pSegmentEnd= pattern.indexOf(pathSeparator, pSegmentStart= pSegmentRestart);
if (pSegmentEnd < 0) {
pSegmentEnd= pLength;
}
fSegmentRestart= filepath.indexOf(pathSeparator, fSegmentRestart + 1);
// skip separator
if (fSegmentRestart < 0) {
fSegmentRestart= fLength;
}
else {
fSegmentRestart++;
}
fSegmentEnd= filepath.indexOf(pathSeparator, fSegmentStart= fSegmentRestart);
if (fSegmentEnd < 0) {
fSegmentEnd= fLength;
}
continue CHECK_SEGMENT;
}
// jump to next segment
pSegmentEnd= pattern.indexOf(pathSeparator, pSegmentStart= pSegmentEnd + 1);
// skip separator
if (pSegmentEnd < 0) {
pSegmentEnd= pLength;
}
fSegmentEnd= filepath.indexOf(pathSeparator, fSegmentStart= fSegmentEnd + 1);
// skip separator
if (fSegmentEnd < 0) {
fSegmentEnd= fLength;
}
}
return ((pSegmentRestart >= pSegmentEnd)
|| (fSegmentStart >= fLength && pSegmentStart >= pLength)
|| (pSegmentStart == pLength - 2
&& pattern.charAt(pSegmentStart) == '*'
&& pattern.charAt(pSegmentStart + 1) == '*')
|| (pSegmentStart == pLength && freeTrailingDoubleStar) );
}
/**
* Answers true if a sub-pattern matches the subpart of the given name, false otherwise.
* char[] pattern matching, accepting wild-cards '*' and '?'. Can match only subset of name/pattern.
* end positions are non-inclusive.
* The subpattern is defined by the patternStart and pattternEnd positions.
* When not case sensitive, the pattern is assumed to already be lowercased, the
* name will be lowercased character per character as comparing.
* <br>
* <br>
* For example:
* <ol>
* <li><pre>
* pattern= { '?', 'b', '*' }
* patternStart= 1
* patternEnd= 3
* name= { 'a', 'b', 'c' , 'd' }
* nameStart= 1
* nameEnd= 4
* isCaseSensitive= true
* result => true
* </pre>
* </li>
* <li><pre>
* pattern= { '?', 'b', '*' }
* patternStart= 1
* patternEnd= 2
* name= { 'a', 'b', 'c' , 'd' }
* nameStart= 1
* nameEnd= 4
* isCaseSensitive= true
* result => false
* </pre>
* </li>
* </ol>
*
* @param pattern the given pattern
* @param patternStart the given pattern start
* @param patternEnd the given pattern end
* @param name the given name
* @param nameStart the given name start
* @param nameEnd the given name end
* @param isCaseSensitive flag to know if the matching should be case sensitive
* @return true if a sub-pattern matches the subpart of the given name, false otherwise
*/
public static final boolean match(
final String pattern,
final int patternStart, int patternEnd,
final String name, final int nameStart, int nameEnd,
final boolean isCaseSensitive) {
if (name == null) {
return false; // null name cannot match
}
if (pattern == null) {
return true; // null pattern is equivalent to '*'
}
int iPattern= patternStart;
int iName= nameStart;
if (patternEnd < 0) {
patternEnd= pattern.length();
}
if (nameEnd < 0) {
nameEnd= name.length();
}
/* check first segment */
char patternChar= 0;
while (true) {
if (iPattern == patternEnd) {
if (iName == nameEnd) {
return true; // the chars match
}
return false; // pattern has ended but not the name, no match
}
if ((patternChar= pattern.charAt(iPattern)) == '*') {
break;
}
if (iName == nameEnd) {
return false; // name has ended but not the pattern
}
if (patternChar != ((isCaseSensitive) ?
name.charAt(iName) :
Character.toLowerCase(name.charAt(iName)) )
&& patternChar != '?') {
return false;
}
iName++;
iPattern++;
}
/* check sequence of star+segment */
int segmentStart;
if (patternChar == '*') {
segmentStart= ++iPattern; // skip star
}
else {
segmentStart= 0; // force iName check
}
int prefixStart= iName;
CHECK_SEGMENT : while (iName < nameEnd) {
if (iPattern == patternEnd) {
iPattern= segmentStart; // mismatch - restart current segment
iName= ++prefixStart;
continue CHECK_SEGMENT;
}
/* segment is ending */
if ((patternChar= pattern.charAt(iPattern)) == '*') {
segmentStart= ++iPattern; // skip start
if (segmentStart == patternEnd) {
return true;
}
prefixStart= iName;
continue CHECK_SEGMENT;
}
/* check current name character */
if (patternChar != ((isCaseSensitive) ?
name.charAt(iName) :
Character.toLowerCase(name.charAt(iName)) )
&& patternChar != '?') {
iPattern= segmentStart; // mismatch - restart current segment
iName= ++prefixStart;
continue CHECK_SEGMENT;
}
iName++;
iPattern++;
}
return ((segmentStart == patternEnd)
|| (iName == nameEnd && iPattern == patternEnd)
|| (iPattern == patternEnd - 1 && pattern.charAt(iPattern) == '*') );
}
}