All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.docker.IgnorePattern Maven / Gradle / Ivy

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.docker;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.netbeans.api.annotations.common.CheckForNull;
import org.openide.util.Pair;

/**
 *
 * @author Petr Hejl
 */
public class IgnorePattern {

    private final List rules;

    private final boolean negative;

    private IgnorePattern(List rules, boolean negative) {
        this.rules = rules;
        this.negative = negative;
    }

    @CheckForNull
    public static IgnorePattern compile(String pattern, Character separator, boolean exclusionSupported) {
        String trimmed = pattern.trim();
        boolean negative = false;
        if (exclusionSupported && trimmed.startsWith("!")) {
            negative = true;
            trimmed = trimmed.substring(1).trim();
        }
        char sep = separator != null ? separator : File.separatorChar;
        String preprocessed = preprocess(trimmed, sep);
        // remove the leading / or \ we will match the relative paths
        if (preprocessed.startsWith(Character.toString(sep))) {
            preprocessed = preprocessed.substring(1);
        }
        return compilePattern(preprocessed, sep, negative);
    }

    static String preprocess(String pattern, char separator) {
        String sep = Character.toString(separator);
        String trimmed = pattern.trim();
        String volume = getVolume(trimmed, separator);
        String path = trimmed.substring(volume.length());
        String ret = path.replaceAll("(" + Pattern.quote(sep) + "){2,}", Matcher.quoteReplacement(sep))
                .replaceAll("(" + Pattern.quote(sep) + "\\.)+(" + Pattern.quote(sep) + "|$)", Matcher.quoteReplacement(sep));
        if (ret.endsWith(sep) && ret.length() > 1) {
            ret = ret.substring(0, ret.length() - sep.length());
        }
        String[] parts = ret.split(Pattern.quote(sep));
        if (parts.length > 1) {
            boolean root = false;
            StringBuilder removed = new StringBuilder();
            int count = 0;
            for (int i = parts.length - 1; i >= 0; i--) {
                if (parts[i].isEmpty()) {
                    root = true;
                    break;
                }
                if (parts[i].equals("..")) {
                    count++;
                } else {
                    if (count == 0) {
                        if (removed.length() > 0) {
                            removed.insert(0, sep);
                        }
                        removed.insert(0, parts[i]);
                    } else {
                        count--;
                    }
                }
            }

            for (int i = 0; i < count; i++) {
                if (removed.length() > 0) {
                    removed.insert(0, sep);
                }
                removed.insert(0, "..");
            }
            if (root) {
                removed.insert(0, sep);
            }
            ret = removed.toString();
        }

        ret = ret.replaceAll("^(" + Pattern.quote(sep) + "\\.\\.)+(" + Pattern.quote(sep) +")?", Matcher.quoteReplacement(sep))
                .replaceAll("/", Matcher.quoteReplacement(sep));
        if (ret.isEmpty()) {
            ret = ".";
        }
        return volume + ret;
    }

    static IgnorePattern compilePattern(String pattern, char separator, boolean negative) {
        String trimmed = pattern.trim();
        List ret = new ArrayList<>();
        char[] patternChars = trimmed.toCharArray();
        List buffer = new ArrayList<>();
        for (int i = 0; i < patternChars.length; i++) {
            char c = patternChars[i];
            switch (c) {
                case '*':
                    addCharacterListRule(ret, buffer);
                    if (ret.isEmpty() || !(ret.get(ret.size() - 1) instanceof StarRule)) {
                        ret.add(new StarRule(separator));
                    }
                    break;
                case '?':
                    addCharacterListRule(ret, buffer);
                    ret.add(new QuestionRule(separator));
                    break;
                case '[':
                    addCharacterListRule(ret, buffer);
                    Pair p = createRange(patternChars, i, separator);
                    ret.add(p.first());
                    if (p.second() < 0) {
                        return new IgnorePattern(ret, negative);
                    }
                    i = p.second();
                    break;
                case '\\':
                    if (separator == '\\') {
                        buffer.add(patternChars[i]);
                    } else {
                        if (i < patternChars.length - 1) {
                            buffer.add(patternChars[++i]);
                        } else {
                            addCharacterListRule(ret, buffer);
                            ret.add(new ErrorRule(trimmed, i));
                            return new IgnorePattern(ret, negative);
                        }
                    }
                    break;
                default:
                    buffer.add(patternChars[i]);
                    break;
            }
        }
        addCharacterListRule(ret, buffer);
        return new IgnorePattern(ret, negative);
    }

    private static void addCharacterListRule(List rules, List buffer) {
        if (!buffer.isEmpty()) {
            rules.add(new CharacterListRule(buffer));
            buffer.clear();
        }
    }

    public boolean matches(String input) throws PatternSyntaxException {
        return matches(rules, input);
    }

    public boolean isNegative() {
        return negative;
    }

    boolean isError() {
        for (Rule r : rules) {
            if (r instanceof ErrorRule) {
                return true;
            }
        }
        return false;
    }

    private static boolean matches(List rules, String input) throws PatternSyntaxException {
        char[] inputChars = input.toCharArray();
        int i = 0;
        int listIndex = 0;
        for (Iterator it = rules.iterator(); it.hasNext();) {
            Rule r = it.next();
            //try {
                if (inputChars.length == 0) {
                    // star matches even empty string
                    return rules.size() == 1 && r.matchesEmpty();
                }
                int[] test = r.consume(inputChars, i);
                if (test == null) {
                    return false;
                }

                if (test.length == 1) {
                    i = test[0];
                } else if (listIndex == rules.size() - 1
                        && test[test.length - 1] >= input.length()) {
                    // last rule - take the longest one
                    i = test[test.length - 1];
                } else {
                    for (int j = test.length - 1; j >= 0; j--) {
                        if (matches(rules.subList(listIndex + 1, rules.size()), input.substring(test[j]))) {
                            return true;
                        }
                    }
                    return false;
                }
//            } catch (PatternSyntaxException ex) {
//                return false;
//            }
            listIndex++;
            if (i >= inputChars.length) {
                if (!it.hasNext()) {
                    return true;
                } else {
                    return it.next().matchesEmpty();
                }
            }
        }
        return i >= inputChars.length;
    }

    private static Pair createRange(char[] chars, int offset, char separator) {
        if (chars[offset] != '[' || offset >= chars.length - 1) {
            return Pair.of(new ErrorRule(new String(chars), offset), -1);
            //throw new PatternSyntaxException("Malformed range", new String(chars), offset);
        }

        boolean negated = false;
        int start = offset + 1;
        char first = chars[offset + 1];
        if (first == '^') {
            negated = true;
            start++;
        }

        if (start >= chars.length - 1) {
            return Pair.of(new ErrorRule(new String(chars), start), -1);
            //throw new PatternSyntaxException("Malformed range", new String(chars), start);
        }

        Character last = null;
        LinkedList singles = new LinkedList<>();
        List> ranges = new LinkedList<>();
        boolean inRange = false;
        for (int i = start; i < chars.length; i++) {
            char c = chars[i];
            switch (c) {
                case '\\':
                    if (separator == '\\') {
                        char l = chars[i];
                        if (inRange) {
                            ranges.add(Pair.of(last, l));
                            inRange = false;
                            last = null;
                        } else {
                            last = l;
                            singles.add(l);
                        }
                    } else {
                        if (i < chars.length - 1) {
                            char l = chars[++i];
                            // XXX is backslash allowed in range ?
                            if (inRange) {
                                ranges.add(Pair.of(last, l));
                                inRange = false;
                                last = null;
                            } else {
                                last = l;
                                singles.add(l);
                            }
                        } else {
                            return Pair.of(new ErrorRule(new String(chars), i), -1);
                            //throw new PatternSyntaxException("Malformed range", new String(chars), i);
                        }
                    }
                    break;
                case ']':
                    if (inRange || i == start) {
                        return Pair.of(new ErrorRule(new String(chars), i), -1);
                        //throw new PatternSyntaxException("Malformed range", new String(chars), i);
                    }
                    return Pair.of(new RangeRule(negated, ranges, singles), i);
                case '-':
                    if (last == null) {
                        return Pair.of(new ErrorRule(new String(chars), i), -1);
                        //throw new PatternSyntaxException("Malformed range", new String(chars), i);
                    }
                    singles.removeLast();
                    inRange = true;
                    break;
                default:
                    char l = chars[i];
                    if (inRange) {
                        ranges.add(Pair.of(last, l));
                        inRange = false;
                        last = null;
                    } else {
                        last = l;
                        singles.add(l);
                    }
                    break;
            }
        }
        return Pair.of(new ErrorRule(new String(chars), chars.length - 1), -1);
        //throw new PatternSyntaxException("Malformed range", new String(chars), chars.length - 1);
    }

    private static String getVolume(String path, char separator) {
        if (separator != '\\') {
            return "";
        }
        if (path.length() < 2) {
            return "";
        }
        char drive = path.charAt(0);
        if (path.charAt(1) == ':' && ('a' <= drive && drive <= 'z' || 'A' <= drive && drive <= 'Z')) { // NOI18N
            return path.substring(0, 2);
        }
        // FIXME UNC

        return "";
    }

    private static interface Rule {

        int[] consume(char[] chars, int offset);

        boolean matchesEmpty();

    }

    private static class StarRule implements Rule {

        private final char separator;

        public StarRule(char separator) {
            this.separator = separator;
        }

        @Override
        public int[] consume(char[] chars, int offset) {
            if (offset >= chars.length) {
                throw new IllegalArgumentException();
            }

            int limit = -1;
            for (int i = offset; i < chars.length; i++) {
                if (chars[i] == separator) {
                    limit = i;
                    break;
                }
            }
            if (limit < 0) {
                limit = chars.length;
            }
            int[] ret = new int[limit - offset + 1];
            for (int i = 0; i < ret.length; i++) {
                ret[i] = offset + i;
            }
            return ret;
        }

        @Override
        public boolean matchesEmpty() {
            return true;
        }
    }

    private static class QuestionRule implements Rule {

        private final char separator;

        public QuestionRule(char separator) {
            this.separator = separator;
        }

        @Override
        public int[] consume(char[] chars, int offset) {
            if (offset >= chars.length) {
                throw new IllegalArgumentException();
            }
            if (chars[offset] == separator) {
                return null;
            }
            return new int[]{offset + 1};
        }

        @Override
        public boolean matchesEmpty() {
            return false;
        }
    }

    private static class RangeRule implements Rule {

        private final boolean negated;

        private final List> ranges;

        private final List singles;

        public RangeRule(boolean negated, List> ranges, List singles) {
            this.negated = negated;
            this.ranges = ranges;
            this.singles = singles;
        }

        @Override
        public int[] consume(char[] chars, int offset) {
            if (offset >= chars.length) {
                throw new IllegalArgumentException();
            }
            boolean ok = check(chars[offset]);
            if (negated) {
                ok = !ok;
            }
            if (!ok) {
                return null;
            }
            return new int[]{offset + 1};
        }

        @Override
        public boolean matchesEmpty() {
            return false;
        }

        private boolean check(char c) {
            for (Character s : singles) {
                if (s == c) {
                    return true;
                }
            }
            for (Pair r : ranges) {
                if (r.first() <= c && c <= r.second()) {
                    return true;
                }
            }
            return false;
        }
    }

    private static class CharacterListRule implements Rule {

        private final List array;

        public CharacterListRule(List array) {
            this.array = new ArrayList<>(array);
        }

        @Override
        public int[] consume(char[] chars, int offset) throws IllegalStateException {
            if (offset >= chars.length) {
                throw new IllegalArgumentException();
            }

            for (int i = 0; i < array.size(); i++) {
                if (array.get(i) != chars[offset + i]) {
                    return null;
                }
            }
            return new int[]{offset + array.size()};
        }

        @Override
        public boolean matchesEmpty() {
            return false;
        }
    }

    private static class ErrorRule implements Rule {

        private final String regex;

        private final int index;

        public ErrorRule(String regex, int index) {
            this.regex = regex;
            this.index = index;
        }

        @Override
        public int[] consume(char[] chars, int offset) {
            throw new PatternSyntaxException("Malformed pattern", regex, index);
        }

        @Override
        public boolean matchesEmpty() {
            return false;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy