001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 *
019 * $Id$
020 */
021
022package org.nuxeo.common.utils;
023
024import java.util.ArrayList;
025import java.util.List;
026
027/**
028 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
029 */
030public class FileNamePattern {
031
032    private boolean wstart = false;
033
034    private boolean wend = false;
035
036    private char[][] parts;
037
038    public FileNamePattern(String pattern) {
039        initialize(pattern);
040    }
041
042    private void initialize(String pattern) {
043        if (pattern == null || pattern.length() == 0) {
044            return;
045        }
046        List<char[]> result = new ArrayList<>();
047        StringBuilder sb = new StringBuilder();
048        char[] chars = pattern.toCharArray();
049        char c = chars[0];
050        int i = 0;
051        if (c == '*') {
052            wstart = true;
053            i = 1;
054        }
055        int len;
056        for (; i < chars.length; i++) {
057            c = chars[i];
058            switch (c) {
059            case '*':
060                len = sb.length();
061                if (len > 0) {
062                    result.add(toCharArray(sb, len));
063                    sb.setLength(0);
064                }
065                break;
066            default:
067                sb.append(c);
068                break;
069            }
070        }
071        if (c == '*') {
072            wend = true;
073        } else {
074            len = sb.length();
075            if (len > 0) {
076                result.add(toCharArray(sb, len));
077            }
078        }
079        parts = result.toArray(new char[result.size()][]);
080    }
081
082    private static char[] toCharArray(StringBuilder sb, int len) {
083        char[] part = new char[len];
084        sb.getChars(0, len, part, 0);
085        return part;
086    }
087
088    public boolean match(String text) {
089        if (parts == null || parts.length == 0) {
090            if (text.length() == 0) { // handle "" empty patterns
091                return true;
092            }
093            return wstart || wend;
094        }
095
096        return match(text.toCharArray(), 0, 0, !wstart);
097    }
098
099    private boolean match(char[] text, int offset, int part, boolean exactMatch) {
100        if (part >= parts.length) {
101            int len = text.length;
102            if (offset > len) {
103                return false;
104            } else if (offset == len) {
105                return true;
106            } else {
107                return wend;
108            }
109        }
110        char[] pattern = parts[part];
111        // no match founds - try next segment matches
112        if (exactMatch) {
113            int k = offset + pattern.length;
114            if (k > text.length) {
115                return false;
116            }
117            if (containsAt(text, offset, pattern)) {
118                return match(text, k, part + 1, false);
119            }
120            return false;
121        }
122        int k = offset;
123        while (true) {
124            k = indexOf(text, pattern, k);
125            if (k == -1) {
126                return false;
127            }
128            if (match(text, k + pattern.length, part + 1, false)) {
129                return true; // matched
130            }
131            // not matched -> continue using next matching segments
132            k++;
133        }
134    }
135
136    /**
137     * Variant of indexOf with ? wildcard.
138     */
139    public static int indexOf(char[] chars, char[] pattern, int offset) {
140        // do not iterate if not needed
141        if (pattern.length > chars.length - offset) {
142            // not enough chars in the chars array starting at offset to match the given pattern
143            return -1;
144        }
145        if (pattern.length == 0) {
146            return 0;
147        }
148        // get the number of possible matching offsets (past over this offset
149        // matching is no more possible)
150        int len = chars.length - pattern.length + 1;
151        START: for (int i = offset; i < len; i++) {
152            char c = pattern[0];
153            if (chars[i] == c || c == '?') {
154                // find first char -> now look to the rest of the pattern
155                int k = 1;
156                for (; k < pattern.length; k++) {
157                    c = pattern[k];
158                    if (chars[k + i] != c && c != '?') {
159                        continue START;
160                    }
161                }
162                return i;
163            }
164        }
165        return -1;
166    }
167
168    /**
169     * Tests whether the given array match the pattern at the given position. Matching allows ? wildcards.
170     */
171    public static boolean containsAt(char[] array, int offset, char[] pattern) {
172        if (offset + pattern.length > array.length) {
173            return false;
174        }
175        if (pattern.length == 0) {
176            return true;
177        }
178        if (array[offset] != pattern[0] && pattern[0] != '?') {
179            return false;
180        }
181        for (int i = 1; i < pattern.length; i++) {
182            int k = offset + i;
183            if (array[k] != pattern[i] && pattern[i] != '?') {
184                return false;
185            }
186        }
187        return true;
188    }
189
190}