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}