/*
 * Copyright 2019 TNG Technology Consulting GmbH
 *
 * Licensed 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 com.tngtech.archunit.library.freeze;

import java.util.regex.Pattern;

import com.tngtech.archunit.ArchConfiguration;
import com.tngtech.archunit.core.MayResolveTypesViaReflection;

import static com.tngtech.archunit.base.ReflectionUtils.newInstanceOf;

class ViolationLineMatcherFactory {
    private static final String FREEZE_LINE_MATCHER_PROPERTY = "freeze.lineMatcher";
    private static final FuzzyLineNumberMatcher DEFAULT_MATCHER = new FuzzyLineNumberMatcher();

    static ViolationLineMatcher create() {
        return ArchConfiguration.get().containsProperty(FREEZE_LINE_MATCHER_PROPERTY)
                ? createInstance(ArchConfiguration.get().getProperty(FREEZE_LINE_MATCHER_PROPERTY))
                : DEFAULT_MATCHER;
    }

    @MayResolveTypesViaReflection(reason = "This is not part of the import process")
    private static ViolationLineMatcher createInstance(String lineMatcherClassName) {
        try {
            return (ViolationLineMatcher) newInstanceOf(Class.forName(lineMatcherClassName));
        } catch (Exception e) {
            String message = String.format("Could not instantiate %s of configured type '%s=%s'",
                    ViolationLineMatcher.class.getSimpleName(), FREEZE_LINE_MATCHER_PROPERTY, lineMatcherClassName);
            throw new StoreInitializationFailedException(message, e);
        }
    }

    private static class FuzzyLineNumberMatcher implements ViolationLineMatcher {
        private static final Pattern LINE_WITHOUT_LINE_NUMBER_PATTERN = Pattern.compile("(.*\\(.*):\\d+\\)$");

        @Override
        public boolean matches(String lineFromFirstViolation, String lineFromSecondViolation) {
            return ignoreLineNumber(lineFromFirstViolation).equals(ignoreLineNumber(lineFromSecondViolation));
        }

        private String ignoreLineNumber(String violation) {
            return LINE_WITHOUT_LINE_NUMBER_PATTERN.matcher(violation).replaceAll("$1");
        }
    }
}

@@ -674,23 +674,58 @@
 til.
-regex.Pa
+Iterator;%0A%0Aimport com.google.common.base.Spli
 tter
-n;%0A
+;
 %0Aimp
@@ -902,16 +902,59 @@
 tanceOf;
+%0Aimport static java.lang.Character.isDigit;
 %0A%0Aclass 
@@ -2135,120 +2135,8 @@
 r %7B%0A
-        private static final Pattern LINE_WITHOUT_LINE_NUMBER_PATTERN = Pattern.compile(%22(.*%5C%5C(.*):%5C%5Cd+%5C%5C)$%22);%0A%0A
     
@@ -2379,147 +2379,1015 @@
     
-private String ignoreLineNumber(String violation) %7B%0A            return LINE_WITHOUT_LINE_NUMBER_PATTERN.matcher(violation).replaceAll(%22$1%22)
+// Would be nicer to use a regex here, but unfortunately that would have a massive performance impact.%0A        // So we do some low level character processing instead.%0A        private String ignoreLineNumber(String violation) %7B%0A            Iterator%3CString%3E parts = Splitter.on(%22:%22).split(violation).iterator();%0A            StringBuilder removedLineNumbers = new StringBuilder(parts.next());%0A            while (parts.hasNext()) %7B%0A                removedLineNumbers.append(removeLineNumberIfPresent(parts.next()));%0A            %7D%0A            return removedLineNumbers.toString();%0A        %7D%0A%0A        private String removeLineNumberIfPresent(String part) %7B%0A            int i = 0;%0A            while (part.length() %3E i && isDigit(part.charAt(i))) %7B%0A                i++;%0A            %7D%0A            boolean foundClosingBracketFollowingDigits = i %3E 0 && part.length() %3E i && part.charAt(i) == ')';%0A            return foundClosingBracketFollowingDigits%0A                    ? part.substring(i)%0A                    : part
 ;%0A  

Improve performance: Using a regex within the default line matcher has a massive performance impact (tested on hibernate core where evaluation time was reduced by 50% with this algorithm)