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}