001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2023-2026 The Enola <https://enola.dev> Authors
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package dev.enola.data.iri.template;
019
020import static java.util.Objects.requireNonNull;
021
022import java.util.AbstractMap.SimpleEntry;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.Comparator;
026import java.util.List;
027import java.util.Map;
028import java.util.Map.Entry;
029import java.util.Optional;
030import java.util.stream.Collectors;
031
032public class URITemplateMatcherChain<T> {
033
034    private final Comparator<Entry<URITemplateSplitter, T>> COMPARATOR =
035            new Comparator<>() {
036                @Override
037                public int compare(
038                        Entry<URITemplateSplitter, T> o1, Entry<URITemplateSplitter, T> o2) {
039                    return o2.getKey().getLength() - o1.getKey().getLength();
040                }
041            };
042
043    // Beware: This needs to be List and not a Set! (Because of how COMPARATOR is implemented;
044    // note how URI Template need to be ordered by length - but two *different* template of
045    // the *SAME* length still both need to be added!
046    private final List<Entry<URITemplateSplitter, T>> splitters;
047    private final List<String> templates;
048
049    public static <T> Builder<T> builder() {
050        return new Builder<T>();
051    }
052
053    public static <T> Builder<T> builderWithExpectedSize(int size) {
054        return new Builder<T>(size);
055    }
056
057    private URITemplateMatcherChain(List<Entry<URITemplateSplitter, T>> splitters) {
058        splitters.sort(COMPARATOR);
059        this.splitters = splitters;
060        this.templates =
061                Collections.unmodifiableList(
062                        splitters.stream()
063                                .map(entry -> entry.getKey().getTemplate())
064                                .collect(Collectors.toList()));
065    }
066
067    public Optional<Entry<T, Map<String, String>>> match(String uri) {
068        for (var splitter : splitters) {
069            var optMap = splitter.getKey().fromString(uri);
070            if (optMap.isPresent()) {
071                return Optional.of(new SimpleEntry<>(splitter.getValue(), optMap.get()));
072            }
073        }
074        return Optional.empty();
075    }
076
077    public List<String> listTemplates() {
078        // Un-comment for debugging:
079        // return splitters.stream()
080        //         .map(entry -> entry.getKey().toString())
081        //         .collect(Collectors.toSet());
082        return templates;
083    }
084
085    // skipcq: JAVA-E0169
086    public static class Builder<T> implements dev.enola.common.Builder<URITemplateMatcherChain<T>> {
087        private Builder() {
088            splitters = new ArrayList<>();
089        }
090
091        private Builder(int initialCapacity) {
092            splitters = new ArrayList<>(initialCapacity);
093        }
094
095        private final List<Entry<URITemplateSplitter, T>> splitters;
096
097        @Override
098        public URITemplateMatcherChain<T> build() {
099            return new URITemplateMatcherChain<>(splitters);
100        }
101
102        public Builder<T> add(String uriTemplate, T key) {
103            requireNonNull(key);
104            requireNonNull(uriTemplate);
105            if (has(uriTemplate)) {
106                throw new IllegalArgumentException("Already added: " + uriTemplate);
107            }
108            splitters.add(new SimpleEntry<>(new URITemplateSplitter(uriTemplate), key));
109            return this;
110        }
111
112        private boolean has(String uriTemplate) {
113            requireNonNull(uriTemplate);
114            for (var splitter : splitters) {
115                var it = splitter.getKey().getTemplate();
116                if (it.equals(uriTemplate)) {
117                    return true;
118                }
119            }
120            return false;
121        }
122    }
123}