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.thing.impl;
019
020import static java.util.Objects.requireNonNull;
021
022import com.google.common.collect.ImmutableMap;
023import com.google.errorprone.annotations.Immutable;
024import com.google.errorprone.annotations.ImmutableTypeParameter;
025import com.google.errorprone.annotations.ThreadSafe;
026
027import dev.enola.thing.HasPredicateIRI;
028import dev.enola.thing.Link;
029import dev.enola.thing.Thing;
030import dev.enola.thing.java.HasType;
031import dev.enola.thing.java.RdfAnnotations;
032import dev.enola.thing.java.TBF;
033
034import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
035
036import org.jspecify.annotations.Nullable;
037
038@Immutable
039@ThreadSafe
040// TODO Make ImmutableThing package private, and let users create them only via the #FACTORY TBF
041public class ImmutableThing extends ImmutablePredicatesObjects implements IImmutableThing {
042
043    private final String iri;
044
045    protected ImmutableThing(
046            String iri,
047            ImmutableMap<String, Object> properties,
048            ImmutableMap<String, String> datatypes) {
049        super(properties, datatypes);
050        this.iri = requireNonNull(iri, "iri");
051    }
052
053    // TODO Is <? extends IImmutableThing> useless on this static method? Same as <IImmutableThing>?
054    public static Thing.Builder<? extends IImmutableThing> builder() {
055        return new Builder<>(ImmutableThing::new);
056    }
057
058    public static Thing.Builder<? extends IImmutableThing> builderWithExpectedSize(
059            int expectedSize) {
060        return new Builder<>(ImmutableThing::new, expectedSize);
061    }
062
063    // TODO Move this into .java package?
064    // TODO Rename ImmutableThing.FACTORY to BUILDER_FACTORY
065    public static final TBF FACTORY =
066            new TBF() {
067                @Override
068                public boolean handles(String typeIRI) {
069                    return true;
070                }
071
072                @Override
073                @SuppressWarnings({"unchecked", "rawtypes"})
074                public Thing.Builder<Thing> create(String typeIRI) {
075                    var builder = builder();
076                    builder.add(HasType.IRI, new Link(typeIRI));
077                    return (Thing.Builder) builder;
078                }
079
080                @Override
081                public boolean handles(Class<?> builderInterface) {
082                    return builderInterface.equals(Thing.Builder.class);
083                }
084
085                @Override
086                @SuppressWarnings("unchecked")
087                public <T extends Thing, B extends Thing.Builder<T>> B create(
088                        Class<B> builderInterface, Class<T> thingInterface) {
089                    var builder = (B) builder();
090                    RdfAnnotations.addType(thingInterface, builder);
091                    return builder;
092                }
093
094                @Override
095                @SuppressWarnings("unchecked")
096                public <T extends Thing, B extends Thing.Builder<T>> B create(
097                        Class<B> builderInterface, Class<T> thingInterface, int expectedSize) {
098                    var builder = (B) builderWithExpectedSize(expectedSize);
099                    RdfAnnotations.addType(thingInterface, builder);
100                    return builder;
101                }
102            };
103
104    public interface Factory {
105        ImmutableThing create(
106                String iri,
107                ImmutableMap<String, Object> properties,
108                ImmutableMap<String, String> datatypes);
109    }
110
111    @Override
112    public String iri() {
113        return iri;
114    }
115
116    @Override
117    public final boolean equals(Object obj) {
118        return ThingHashCodeEqualsToString.equals(this, obj);
119    }
120
121    @Override
122    public final int hashCode() {
123        return ThingHashCodeEqualsToString.hashCode(this);
124    }
125
126    @Override
127    public final String toString() {
128        return ThingHashCodeEqualsToString.toString(this, properties, datatypes);
129    }
130
131    @Override
132    public Thing.Builder<? extends IImmutableThing> copy() {
133        return new Builder<>(ImmutableThing::new, iri(), properties(), datatypes());
134    }
135
136    // TODO make inner class ImmutableThing.Builder protected
137    @SuppressFBWarnings("NM_SAME_SIMPLE_NAME_AS_INTERFACE")
138    public static class Builder<B extends IImmutableThing> // skipcq: JAVA-E0169
139            extends MutablePredicatesObjectsBuilder<B> implements Thing.Builder<B> {
140
141        private final Factory factory;
142
143        protected @Nullable String iri;
144
145        protected Builder() {
146            this(ImmutableThing::new);
147        }
148
149        protected Builder(Factory factory) {
150            super();
151            this.factory = factory;
152        }
153
154        protected Builder(Factory factory, int expectedSize) {
155            super(expectedSize);
156            this.factory = factory;
157        }
158
159        protected Builder(
160                Factory factory,
161                String iri,
162                final ImmutableMap<String, Object> properties,
163                final ImmutableMap<String, String> datatypes) {
164            super(properties, datatypes);
165            this.factory = factory;
166            iri(iri);
167        }
168
169        @Override
170        public Thing.Builder<B> iri(String iri) {
171            if (this.iri != null && !this.iri.equals((iri)))
172                throw new IllegalStateException(
173                        "IRI already set: " + this.iri + ", cannot set to: " + iri);
174            this.iri = requireNonNull(iri);
175            return this;
176        }
177
178        @Override
179        public <@ImmutableTypeParameter T> Thing.Builder<B> set(String predicateIRI, T value) {
180            super.set(predicateIRI, value);
181            return this;
182        }
183
184        @Override
185        public <@ImmutableTypeParameter T> Thing.Builder<B> set(
186                String predicateIRI, T value, @Nullable String datatypeIRI) {
187            super.set(predicateIRI, value, datatypeIRI);
188            return this;
189        }
190
191        @Override
192        public <@ImmutableTypeParameter T> Thing.Builder<B> add(String predicateIRI, T value) {
193            super.add(predicateIRI, value);
194            return this;
195        }
196
197        @Override
198        public <@ImmutableTypeParameter T> Thing.Builder<B> addAll(
199                String predicateIRI, Iterable<T> values) {
200            super.addAll(predicateIRI, values);
201            return this;
202        }
203
204        @Override
205        public <@ImmutableTypeParameter T> Thing.Builder<B> add(
206                String predicateIRI, T value, @Nullable String datatypeIRI) {
207            super.add(predicateIRI, value, datatypeIRI);
208            return this;
209        }
210
211        @Override
212        public <@ImmutableTypeParameter T> Thing.Builder<B> addAll(
213                String predicateIRI, Iterable<T> values, @Nullable String datatypeIRI) {
214            super.addAll(predicateIRI, values, datatypeIRI);
215            return this;
216        }
217
218        @Override
219        public <@ImmutableTypeParameter T> Thing.Builder<B> addOrdered(
220                String predicateIRI, T value) {
221            super.addOrdered(predicateIRI, value);
222            return this;
223        }
224
225        @Override
226        public <@ImmutableTypeParameter T> Thing.Builder<B> addOrdered(
227                String predicateIRI, T value, @Nullable String datatypeIRI) {
228            super.addOrdered(predicateIRI, value, datatypeIRI);
229            return this;
230        }
231
232        @Override
233        public <@ImmutableTypeParameter T> Thing.Builder<B> add(
234                HasPredicateIRI predicate, T value) {
235            super.add(predicate, value);
236            return this;
237        }
238
239        @Override
240        public <@ImmutableTypeParameter T> Thing.Builder<B> add(
241                HasPredicateIRI predicate, T value, @Nullable String datatypeIRI) {
242            super.add(predicate, value, datatypeIRI);
243            return this;
244        }
245
246        @Override
247        public <@ImmutableTypeParameter T> Thing.Builder<B> addAll(
248                HasPredicateIRI predicate, Iterable<T> value) {
249            super.addAll(predicate, value);
250            return this;
251        }
252
253        @Override
254        public <@ImmutableTypeParameter T> Thing.Builder<B> addAll(
255                HasPredicateIRI predicate, Iterable<T> value, @Nullable String datatypeIRI) {
256            super.addAll(predicate, value, datatypeIRI);
257            return this;
258        }
259
260        @Override
261        public <@ImmutableTypeParameter T> Thing.Builder<B> addOrdered(
262                HasPredicateIRI predicate, T value) {
263            super.addOrdered(predicate, value);
264            return this;
265        }
266
267        @Override
268        public <@ImmutableTypeParameter T> Thing.Builder<B> addOrdered(
269                HasPredicateIRI predicate, T value, @Nullable String datatypeIRI) {
270            super.addOrdered(predicate, value, datatypeIRI);
271            return this;
272        }
273
274        @Override
275        @SuppressWarnings("unchecked")
276        public B build() {
277            if (iri == null)
278                throw new IllegalStateException(PackageLocalConstants.NEEDS_IRI_MESSAGE);
279
280            var pair = ImmutableObjects.build(properties, datatypes);
281            return (B) factory.create(iri, pair.properties(), pair.datatypes());
282        }
283    }
284}