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.metadata; 019 020import com.google.common.collect.ImmutableList; 021 022import dev.enola.thing.KIRI; 023import dev.enola.thing.Link; 024import dev.enola.thing.Thing; 025 026import java.net.URI; 027import java.util.ArrayList; 028import java.util.Optional; 029 030/** 031 * Provider of _"hierarchical"_ (e.g. parent / child) relationships between Things. 032 * 033 * <p>This can be used e.g. to "cluster" things in visual graph diagram representations. 034 */ 035public class ThingHierarchyProvider { 036 037 private final String description; 038 private final Iterable<String> propertyIRIs; 039 040 public ThingHierarchyProvider() { 041 // TODO Same as in ThingTimeProvider, properties eventually won't be hard-coded anymore 042 this( 043 "By Parent Hierarchy:", 044 ImmutableList.of( 045 "https://enola.dev/parent", 046 "https://enola.dev/files/Node/parent", // TODO Node.parent_IRI, 047 KIRI.RDF.TYPE, 048 "http://www.w3.org/2000/01/rdf-schema#subClassOf", 049 "http://www.w3.org/2000/01/rdf-schema#subPropertyOf", 050 "https://enola.dev/origin")); 051 } 052 053 public ThingHierarchyProvider(String description, Iterable<String> propertyIRIs) { 054 this.description = description; 055 this.propertyIRIs = propertyIRIs; 056 } 057 058 public String description() { 059 return description; 060 } 061 062 public Iterable<String> parents(Thing thing) { 063 var list = new ArrayList<String>(); 064 for (var propertyIRI : propertyIRIs) { 065 if (thing.isIterable(propertyIRI)) { 066 var parents = thing.get(propertyIRI, Iterable.class); 067 if (parents == null) continue; 068 for (var parent : parents) { 069 // TODO Same story as in GraphvizGenerator 070 list.add(parent.toString()); 071 } 072 } else { 073 var parent = thing.getString(propertyIRI); 074 if (parent != null) list.add(parent); 075 } 076 } 077 return list; 078 } 079 080 public Optional<String> parent(Thing thing) { 081 for (var propertyIRI : propertyIRIs) { 082 if (thing.isIterable(propertyIRI)) { 083 var parents = thing.get(propertyIRI, Iterable.class); 084 if (parents == null) continue; 085 var iterator = parents.iterator(); 086 if (!iterator.hasNext()) continue; 087 var parent = iterator.next(); 088 return switch (parent) { 089 // TODO This is a kind of converter, and shouldn't be here? 090 case String string -> Optional.of(string); 091 case URI uri -> Optional.of(uri.toString()); 092 case Link link -> Optional.of(link.iri()); 093 default -> 094 throw new IllegalStateException( 095 "Parent of unexpected type: " 096 + parent.getClass() 097 + " : " 098 + parent); 099 }; 100 } else { 101 var parent = thing.getString(propertyIRI); 102 if (parent != null) return Optional.of(parent); 103 } 104 } 105 return Optional.empty(); 106 } 107}