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

com.metaeffekt.artifact.analysis.version.curation.ConditionalCuratedVersionPartsExtractor Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2021-2024 the original author or authors.
 *
 * 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.metaeffekt.artifact.analysis.version.curation;

import com.metaeffekt.artifact.analysis.utils.StringUtils;
import com.metaeffekt.artifact.analysis.version.curation.functions.CuratedVersionFunction;
import com.metaeffekt.artifact.analysis.version.token.VersionToken;
import com.metaeffekt.artifact.analysis.version.token.VersionTokenType;
import com.metaeffekt.artifact.analysis.version.token.VersionTokenizer;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class ConditionalCuratedVersionPartsExtractor {

    private final static Logger LOG = LoggerFactory.getLogger(ConditionalCuratedVersionPartsExtractor.class);

    private final VersionContextMatcher matcher;

    private final Pattern pattern;

    private final String specVersion;
    private final String semVersion;
    private final String buildVersion;
    private final String versionModifier;
    private final String otherVersionPart;
    private final String afterAllPart;
    private final String preprocessingPattern;

    private final List postProcessingFunctions;

    public ConditionalCuratedVersionPartsExtractor(VersionContextMatcher matcher, String pattern, int patternFlags, String specVersion, String semVersion, String buildVersion, String versionModifier, String otherVersionPart, String afterAllPart, String preprocessingPattern, List postProcessingFunctions) {
        this.matcher = matcher;
        this.pattern = pattern == null ? null : Pattern.compile(pattern, patternFlags);
        this.specVersion = specVersion;
        this.semVersion = semVersion;
        this.buildVersion = buildVersion;
        this.versionModifier = versionModifier;
        this.otherVersionPart = otherVersionPart;
        this.afterAllPart = afterAllPart;
        this.preprocessingPattern = preprocessingPattern;
        this.postProcessingFunctions = postProcessingFunctions;
    }

    private static int parsePatternFlags(String patternFlagsString) {
        int patternFlags = 0;
        if (patternFlagsString != null) {
            for (char c : patternFlagsString.toCharArray()) {
                switch (c) {
                    case 'i':
                        patternFlags |= Pattern.CASE_INSENSITIVE;
                        break;
                    case 'm':
                        patternFlags |= Pattern.MULTILINE;
                        break;
                    case 's':
                        patternFlags |= Pattern.DOTALL;
                        break;
                    case 'u':
                        patternFlags |= Pattern.UNICODE_CASE;
                        break;
                    case 'x':
                        patternFlags |= Pattern.COMMENTS;
                        break;
                    default:
                        throw new IllegalArgumentException("Unknown pattern flag: " + c);
                }
            }
        }
        return patternFlags;
    }

    public ExtractedCuratedVersionParts applyFirstStep(String version, VersionContext context) {
        if (this.matcher != null && !this.matcher.matches(context)) {
            return null;
        }

        final Matcher matcher = matcherForExtractorPattern(version);

        if (matcher != null && matcher.matches()) {
            if (preprocessingPattern != null) {
                return new ExtractedCuratedVersionParts(applyPreprocessingPattern(matcher));

            } else {
                return new ExtractedCuratedVersionParts(
                        applySpecVersion(matcher),
                        applySemVersion(matcher),
                        applyBuildVersion(matcher),
                        applyVersionModifier(matcher),
                        applyOtherVersionPart(matcher),
                        applyAfterAllPart(matcher));
            }
        }

        return null;
    }

    public void applySecondStep(ExtractedCuratedVersionParts parts) {
        if (parts == null) {
            return;
        }

        for (CuratedVersionFunction function : postProcessingFunctions) {
            function.apply(parts);
        }
    }

    public boolean matches(String version, VersionContext context) {
        if (this.matcher != null && !this.matcher.matches(context)) {
            return false;
        }

        if (this.pattern == null) {
            return true;
        }

        final Matcher matcher = matcherForExtractorPattern(version);
        return matcher != null && matcher.matches();
    }

    private Matcher matcherForExtractorPattern(String version) {
        if (version == null || this.pattern == null) return null;
        return this.pattern.matcher(version);
    }

    private VersionToken applySpecVersion(Matcher matcher) {
        return applyTokenization(applyPattern(matcher, specVersion));
    }

    private VersionToken applySemVersion(Matcher matcher) {
        return applyTokenization(applyPattern(matcher, semVersion));
    }

    private VersionToken applyBuildVersion(Matcher matcher) {
        return applyTokenization(applyPattern(matcher, buildVersion));
    }

    private VersionToken applyVersionModifier(Matcher matcher) {
        return applyTokenization(applyPattern(matcher, versionModifier));
    }

    private VersionToken applyOtherVersionPart(Matcher matcher) {
        return applyTokenization(applyPattern(matcher, otherVersionPart));
    }

    private VersionToken applyAfterAllPart(Matcher matcher) {
        return applyTokenization(applyPattern(matcher, afterAllPart));
    }

    private String applyPreprocessingPattern(Matcher matcher) {
        return applyPattern(matcher, preprocessingPattern);
    }

    private VersionToken applyTokenization(String str) {
        final List tokenizedVersion = VersionTokenizer.tokenize(str, null);
        if (tokenizedVersion.size() == 1) {
            return tokenizedVersion.get(0);
        } else if (!tokenizedVersion.isEmpty()) {
            return new VersionToken(str, VersionTokenType.STRING);
        }
        return null;
    }

    private String applyPattern(Matcher matcher, String replacement) {
        if (replacement == null) return null;
        return matcher.replaceAll(replacement);
    }

    public JSONObject toJson() {
        return new JSONObject()
                .put("pattern", this.pattern == null ? null : this.pattern.pattern())
                .put("pattern-flags", this.pattern == null ? null : this.pattern.flags())
                .put("segments", new JSONObject()
                        .put("spec", specVersion)
                        .put("sem", semVersion)
                        .put("build", buildVersion)
                        .put("modifier", versionModifier)
                        .put("other", otherVersionPart)
                        .put("after-all", afterAllPart)
                        .put("full", preprocessingPattern))
                .put("context", matcher == null ? null : matcher.toJson())
                .put("second-part-functions", postProcessingFunctions.stream().map(CuratedVersionFunction::toJson).collect(Collectors.toList()));
    }

    public static ConditionalCuratedVersionPartsExtractor fromYaml(Map yaml) {
        final String pattern = (String) yaml.get("pattern");

        final String patternFlagsString = (String) yaml.get("pattern-flags");
        // extract every char that is a valid flag and create a flag int
        int patternFlags = parsePatternFlags(patternFlagsString);

        final Map segments = (Map) yaml.get("segments");

        final String specVersion;
        final String semVersion;
        final String buildVersion;
        final String versionModifier;
        final String otherVersionPart;
        final String afterAllPart;

        if (segments != null) {
            specVersion = (String) segments.get("spec");
            semVersion = (String) segments.get("sem");
            buildVersion = (String) segments.get("build");
            versionModifier = (String) segments.get("modifier");
            otherVersionPart = (String) segments.get("other");
            afterAllPart = (String) segments.get("after-all");
        } else {
            specVersion = null;
            semVersion = null;
            buildVersion = null;
            versionModifier = null;
            otherVersionPart = null;
            afterAllPart = null;
        }

        final String preprocessorPart = (String) yaml.get("preprocessor");

        final VersionContextMatcher matcher;
        if (yaml.containsKey("context")) {
            if (yaml.get("context") instanceof LinkedHashMap) {
                matcher = VersionContextMatcher.fromYaml((Map) yaml.get("context"));
            } else {
                LOG.warn("Invalid context definition in extractor: {}", yaml.get("context"));
                matcher = null;
            }
        } else {
            matcher = null;
        }

        final String effectivePattern = StringUtils.isEmpty(pattern) ? null
                : pattern
                .replace("__YEAR__", __YEAR__.pattern())
                .replace("__SEMVER__", __SEMVER__.pattern())
                .replace("__LETTER__", __LETTER__.pattern())
                .replace("__SEPARATOR__", __SEPARATOR__.pattern())
                .replace("__NUMBER__", __NUMBER__.pattern());

        final List postProcessingFunctions = new ArrayList<>();
        if (yaml.containsKey("functions") && yaml.get("functions") instanceof List) {
            for (Object function : (List) yaml.get("functions")) {
                if (function instanceof LinkedHashMap) {
                    postProcessingFunctions.add(CuratedVersionFunction.fromYaml((LinkedHashMap) function));
                }
            }
        }
        postProcessingFunctions.removeIf(Objects::isNull);

        return new ConditionalCuratedVersionPartsExtractor(matcher, effectivePattern, patternFlags,
                specVersion, semVersion, buildVersion, versionModifier, otherVersionPart, afterAllPart,
                preprocessorPart,
                postProcessingFunctions
        );
    }

    private final static Pattern __YEAR__ = Pattern.compile("[0-9]{4}");
    private final static Pattern __SEMVER__ = Pattern.compile("[0-9]+\\.[0-9]+(?:\\.[0-9]+)*?");
    private final static Pattern __NUMBER__ = Pattern.compile("[0-9]+");
    private final static Pattern __LETTER__ = Pattern.compile("[a-zA-Z]+");
    private final static Pattern __SEPARATOR__ = Pattern.compile("[-_ :]");
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy