001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2024-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;
019
020import com.google.common.collect.ImmutableCollection;
021import com.google.common.collect.ImmutableList;
022import com.google.common.collect.ImmutableSet;
023import com.google.common.collect.ImmutableSortedMap;
024
025import dev.enola.common.Builder;
026
027import org.jspecify.annotations.Nullable;
028
029import java.util.HashMap;
030import java.util.Map;
031
032/**
033 * RepositoryBuilder builds immutable {@link Repository} instances.
034 *
035 * <p>This Builder class itself is NOT thread-safe. The {@link Repository} returned by its {@link
036 * #build()} however is thread-safe (simply because it's immutable). Use {@link MemoryRepositoryRW}
037 * for a thread-safe {@link Store}.
038 */
039public abstract class RepositoryBuilder<T> extends AbstractMapRepositoryRW<T>
040        implements RepositoryRW<T>, Builder<Repository<T>> {
041
042    private final Map<String, T> map = new HashMap<>();
043
044    protected RepositoryBuilder(ImmutableList<Trigger<? extends T>> triggers) {
045        super(triggers);
046    }
047
048    // TODO @Deprecated
049    protected RepositoryBuilder() {
050        this(ImmutableList.of());
051    }
052
053    @Override
054    protected Map<String, T> map() {
055        return map;
056    }
057
058    @Override
059    public RepositoryBuilder<T> store(T item) {
060        super.store(item);
061        return this;
062    }
063
064    @Override
065    public RepositoryBuilder<T> storeAll(Iterable<T> items) { // skipcq: JAVA-W1016
066        super.storeAll(items);
067        return this;
068    }
069
070    @Override
071    public Repository<T> build() {
072        return new RepositoryImpl<>(buildMap());
073    }
074
075    protected ImmutableSortedMap<String, T> buildMap() {
076        return ImmutableSortedMap.<String, T>naturalOrder().putAll(map).buildOrThrow();
077    }
078
079    protected <O> O require(O what, String identification) {
080        if (what == null) throw new IllegalArgumentException("Missing required: " + identification);
081        if (what instanceof String whatString) {
082            if (whatString.trim().isEmpty())
083                throw new IllegalArgumentException("Empty: " + identification);
084        }
085        return what;
086    }
087
088    protected static class RepositoryImpl<T> implements Repository<T> {
089        private final ImmutableSortedMap<String, T> items;
090
091        protected RepositoryImpl(ImmutableSortedMap<String, T> items) {
092            this.items = items;
093        }
094
095        @Override
096        public ImmutableSet<String> listIRI() {
097            return items.keySet();
098        }
099
100        @Override
101        public @Nullable T get(String iri) {
102            return items.get(iri);
103        }
104
105        @Override
106        public ImmutableCollection<T> list() {
107            return items.values();
108        }
109
110        @Override
111        public String toString() {
112            return "RepositoryImpl{" + "items=" + items.keySet() + '}';
113        }
114    }
115}