/******************************************************************************* * Copyright (c) 2000, 2006 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.compare.internal; import com.ibm.icu.text.MessageFormat; import java.util.StringTokenizer; import org.eclipse.core.resources.*; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.runtime.IStatus; public class CompareFilter { private static final char[][] NO_CHAR_CHAR= new char[0][]; private char[][] fExtraResourceFileFilters; private String[] fExtraResourceFolderFilters; public CompareFilter() { // nothing to do } /* * Returns true if path matches filter, that is if path should be filtered. */ public boolean filter(String path0, boolean folder, boolean isArchive) { if (!folder && fExtraResourceFileFilters != null) { char[] name= path0.toCharArray(); for (int i= 0, l= fExtraResourceFileFilters.length; i < l; i++) if (match(fExtraResourceFileFilters[i], name, true)) return true; } if (folder && fExtraResourceFolderFilters != null) { for (int i= 0, l= fExtraResourceFolderFilters.length; i < l; i++) if (fExtraResourceFolderFilters[i].equals(path0)) return true; } return false; } public static String validateResourceFilters(String text) { IWorkspace workspace= ResourcesPlugin.getWorkspace(); String[] filters= getTokens(text, ","); //$NON-NLS-1$ for (int i= 0; i < filters.length; i++) { String fileName= filters[i].replace('*', 'x'); int resourceType= IResource.FILE; int lastCharacter= fileName.length() - 1; if (lastCharacter >= 0 && fileName.charAt(lastCharacter) == '/') { fileName= fileName.substring(0, lastCharacter); resourceType= IResource.FOLDER; } IStatus status= workspace.validateName(fileName, resourceType); if (status.matches(IStatus.ERROR)) { String format= Utilities.getString("ComparePreferencePage.filter.invalidsegment.error"); //$NON-NLS-1$ return MessageFormat.format(format, new String[] { status.getMessage() } ); } } return null; } public void setFilters(String filterSequence) { char[][] filters= filterSequence != null && filterSequence.length() > 0 ? splitAndTrimOn(',', filterSequence.toCharArray()) : null; if (filters == null) { fExtraResourceFileFilters= null; fExtraResourceFolderFilters= null; } else { int fileCount= 0, folderCount= 0; for (int i= 0, l= filters.length; i < l; i++) { char[] f= filters[i]; if (f.length == 0) continue; if (f[f.length - 1] == '/') folderCount++; else fileCount++; } fExtraResourceFileFilters= new char[fileCount][]; fExtraResourceFolderFilters= new String[folderCount]; for (int i= 0, l= filters.length; i < l; i++) { char[] f= filters[i]; if (f.length == 0) continue; if (f[f.length - 1] == '/') fExtraResourceFolderFilters[--folderCount]= new String(subarray(f, 0, f.length - 1)); else fExtraResourceFileFilters[--fileCount]= f; } } } ///////// private static String[] getTokens(String text, String separator) { StringTokenizer tok= new StringTokenizer(text, separator); int nTokens= tok.countTokens(); String[] res= new String[nTokens]; for (int i= 0; i < res.length; i++) res[i]= tok.nextToken().trim(); return res; } /** * Answers true if the pattern matches the given name, false otherwise. * This char[] pattern matching accepts wild-cards '*' and '?'. * * When not case sensitive, the pattern is assumed to already be * lowercased, the name will be lowercased character per character as * comparing. If name is null, the answer is false. If pattern is null, the * answer is true if name is not null. <br><br>For example: * <ol> * <li> * * <pre> * pattern = { '?', 'b', '*' } name = { 'a', 'b', 'c' , 'd' } isCaseSensitive = true result => true * </pre> * * * </li> * <li> * * <pre> * pattern = { '?', 'b', '?' } name = { 'a', 'b', 'c' , 'd' } isCaseSensitive = true result => false * </pre> * * * </li> * <li> * * <pre> * pattern = { 'b', '*' } name = { 'a', 'b', 'c' , 'd' } isCaseSensitive = true result => false * </pre> * * * </li> * </ol> * * @param pattern * the given pattern * @param name * the given name * @param isCaseSensitive * flag to know whether or not the matching should be case * sensitive * @return true if the pattern matches the given name, false otherwise */ private boolean match(char[] pattern, char[] name, boolean isCaseSensitive) { if (name == null) return false; // null name cannot match if (pattern == null) return true; // null pattern is equivalent to '*' return match(pattern, 0, pattern.length, name, 0, name.length, isCaseSensitive); } /** * Answers true if the 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 = 2 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 the a sub-pattern matches the subpart of the given name, * false otherwise */ private boolean match(char[] pattern, int patternStart, int patternEnd, char[] name, int nameStart, int nameEnd, 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 ((iPattern < patternEnd) && (patternChar= pattern[iPattern]) != '*') { if (iName == nameEnd) return false; if (patternChar != (isCaseSensitive ? name[iName] : Character.toLowerCase(name[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; checkSegment : while (iName < nameEnd) { if (iPattern == patternEnd) { iPattern= segmentStart; // mismatch - restart current segment iName= ++prefixStart; continue checkSegment; } /* segment is ending */ if ((patternChar= pattern[iPattern]) == '*') { segmentStart= ++iPattern; // skip start if (segmentStart == patternEnd) { return true; } prefixStart= iName; continue checkSegment; } /* check current name character */ if ((isCaseSensitive ? name[iName] : Character.toLowerCase(name[iName])) != patternChar && patternChar != '?') { iPattern= segmentStart; // mismatch - restart current segment iName= ++prefixStart; continue checkSegment; } iName++; iPattern++; } return (segmentStart == patternEnd) || (iName == nameEnd && iPattern == patternEnd) || (iPattern == patternEnd - 1 && pattern[iPattern] == '*'); } /** * Return a new array which is the split of the given array using the given * divider and triming each subarray to remove whitespaces equals to ' '. * <br><br>For example: * <ol> * <li> * * <pre> * divider = 'b' array = { 'a' , 'b', 'b', 'a', 'b', 'a' } result => { { 'a' }, { }, { 'a' }, { 'a' } } * </pre> * * * </li> * <li> * * <pre> * divider = 'c' array = { 'a' , 'b', 'b', 'a', 'b', 'a' } result => { { 'a', 'b', 'b', 'a', 'b', 'a' } } * </pre> * * * </li> * <li> * * <pre> * divider = 'b' array = { 'a' , ' ', 'b', 'b', 'a', 'b', 'a' } result => { { 'a' }, { }, { 'a' }, { 'a' } } * </pre> * * * </li> * <li> * * <pre> * divider = 'c' array = { ' ', ' ', 'a' , 'b', 'b', 'a', 'b', 'a', ' ' } result => { { 'a', 'b', 'b', 'a', 'b', 'a' } } * </pre> * * * </li> * </ol> * * @param divider * the given divider * @param array * the given array * @return a new array which is the split of the given array using the * given divider and triming each subarray to remove whitespaces * equals to ' ' */ private char[][] splitAndTrimOn(char divider, char[] array) { int length= array == null ? 0 : array.length; if (length == 0) return NO_CHAR_CHAR; int wordCount= 1; for (int i= 0; i < length; i++) if (array[i] == divider) wordCount++; char[][] split= new char[wordCount][]; int last= 0, currentWord= 0; for (int i= 0; i < length; i++) { if (array[i] == divider) { int start= last, end= i - 1; while (start < i && array[start] == ' ') start++; while (end > start && array[end] == ' ') end--; split[currentWord]= new char[end - start + 1]; System.arraycopy(array, start, split[currentWord++], 0, end - start + 1); last= i + 1; } } int start= last, end= length - 1; while (start < length && array[start] == ' ') start++; while (end > start && array[end] == ' ') end--; split[currentWord]= new char[end - start + 1]; System.arraycopy(array, start, split[currentWord++], 0, end - start + 1); return split; } /** * Answers a new array which is a copy of the given array starting at the * given start and ending at the given end. The given start is inclusive * and the given end is exclusive. Answers null if start is greater than * end, if start is lower than 0 or if end is greater than the length of * the given array. If end equals -1, it is converted to the array length. * <br><br>For example: * <ol> * <li> * * <pre> * array = { 'a' , 'b' } start = 0 end = 1 result => { 'a' } * </pre> * * * </li> * <li> * * <pre> * array = { 'a', 'b' } start = 0 end = -1 result => { 'a' , 'b' } * </pre> * * * </li> * </ol> * * @param array * the given array * @param start * the given starting index * @param end * the given ending index * @return a new array which is a copy of the given array starting at the * given start and ending at the given end * @exception NullPointerException * if the given array is null */ private char[] subarray(char[] array, int start, int end) { if (end == -1) end= array.length; if (start > end) return null; if (start < 0) return null; if (end > array.length) return null; char[] result= new char[end - start]; System.arraycopy(array, start, result, 0, end - start); return result; } }