001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2025-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.id; 019 020import static java.util.Objects.requireNonNull; 021 022import com.google.errorprone.annotations.Immutable; 023 024import dev.enola.common.convert.ConversionException; 025import dev.enola.common.convert.ObjectToStringBiConverter; 026import dev.enola.data.iri.IRIConverter; 027import dev.enola.data.iri.StringableIRI; 028 029import org.jspecify.annotations.Nullable; 030 031import java.io.IOException; 032import java.util.Optional; 033 034@Immutable(containerOf = "T") 035/** ID-IRI is an {@link IRI} based on an ID object. */ 036// skipcq: JAVA-W0100 037public abstract class IDIRI<T extends Comparable<T>> extends StringableIRI { 038 // TODO dev.enola.data.id.IDIRI to dev.enola.data.iri 039 040 // TODO Add URL Pattern, in & out! 041 042 private final T id; 043 044 protected IDIRI(T id) { 045 this.id = requireNonNull(id); 046 } 047 048 // TODO Consider "pulling up" an Object id() method to IRI itself? 049 public T id() { 050 return id; 051 } 052 053 protected abstract <C extends IDIRI<T>> IRIConverter<C> iriConverter(); 054 055 @Override 056 public void append(Appendable appendable) throws IOException { 057 if (!iriConverter().convertInto(this, appendable)) 058 throw new IllegalArgumentException(id.toString()); 059 } 060 061 @Override 062 protected String createStringIRI() { 063 return iriConverter().convertTo(this); 064 } 065 066 @Override 067 protected boolean isEqualTo(Object other) { 068 return id.equals(other); 069 } 070 071 @Override 072 public int hashCode() { 073 return id.hashCode(); 074 } 075 076 @Override 077 @SuppressWarnings("unchecked") 078 protected int compare(Object other) { 079 return id.compareTo(((IDIRI<T>) other).id); 080 } 081 082 protected abstract static class ConverterX<C extends IDIRI<T>, T extends Comparable<T>> 083 implements IRIConverter<C> { 084 private final String prefix; 085 private final ObjectToStringBiConverter<T> converter; 086 087 protected ConverterX(String prefix, ObjectToStringBiConverter<T> converter) { 088 this.prefix = prefix; 089 this.converter = converter; 090 } 091 092 protected abstract C create(T id); 093 094 @Override 095 public Optional<C> convert(String input) throws ConversionException { 096 if (!input.startsWith(prefix)) return Optional.empty(); 097 @Nullable T id = converter.convertFrom(input.substring(prefix.length())); 098 if (id == null) 099 throw new ConversionException("Not a TestID: " + input); // Optional.empty()? 100 return Optional.of(create(id)); 101 } 102 103 @Override 104 public boolean convertInto(C from, Appendable into) 105 throws ConversionException, IOException { 106 into.append(prefix); 107 return converter.convertInto(from.id(), into); 108 } 109 } 110}