001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*- 002 * 003 * Copyright © 2025 microBean™. 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on 011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 012 * specific language governing permissions and limitations under the License. 013 */ 014package org.microbean.bean.model; 015 016import java.util.Collection; 017import java.util.List; 018import java.util.Map; 019import java.util.Set; 020 021import java.util.function.BiFunction; 022import java.util.function.Function; 023 024import org.microbean.assign.Aggregate; 025import org.microbean.assign.AttributedElement; 026import org.microbean.assign.AttributedType; 027import org.microbean.assign.Selectable; 028 029import org.microbean.bean.Bean; 030 031import static java.util.HashMap.newHashMap; 032 033import static java.util.HashSet.newHashSet; 034 035import static java.util.Objects.requireNonNull; 036 037import static java.util.stream.Collectors.toUnmodifiableSet; 038 039public final class Model { 040 041 private final Set<DependencyResolution> dependencyResolutions; 042 043 private volatile boolean valid; 044 045 private Model() { 046 this(Set.of()); 047 } 048 049 private Model(Set<? extends DependencyResolution> dependencyResolutions) { 050 super(); 051 if (dependencyResolutions == null || dependencyResolutions.isEmpty()) { 052 this.dependencyResolutions = Set.of(); 053 this.valid = true; 054 } else { 055 this.dependencyResolutions = Set.copyOf(dependencyResolutions); 056 } 057 } 058 059 @Override // Object 060 public final boolean equals(final Object other) { 061 return switch (other) { 062 case null -> false; 063 case Model m when this.getClass() == m.getClass() -> this.dependencyResolutions.equals(m.dependencyResolutions); 064 default -> false; 065 }; 066 } 067 068 @Override // Object 069 public final int hashCode() { 070 return this.dependencyResolutions.hashCode(); 071 } 072 073 public final boolean valid() { 074 if (this.valid) { // volatile read 075 return true; 076 } 077 for (final DependencyResolution dependencyResolution : this.dependencyResolutions) { 078 if (dependencyResolution.beans().size() != 1) { 079 return false; 080 } 081 } 082 this.valid = true; // volatile write 083 return true; 084 } 085 086 public final Set<DependencyResolution> ambiguousDependencyResolutions() { 087 return this.dependencyResolutions.stream().filter(DependencyResolution::ambiguous).collect(toUnmodifiableSet()); 088 } 089 090 public final Set<DependencyResolution> unsatisfiedDependencyResolutions() { 091 return this.dependencyResolutions.stream().filter(DependencyResolution::unsatisfied).collect(toUnmodifiableSet()); 092 } 093 094 // So you can do: 095 // org.microbean.assign.Selectables.caching(selectable, model.toSelectionCache()); 096 public final BiFunction<? super AttributedType, Function<? super AttributedType, ? extends List<Bean<?>>>, ? extends List<Bean<?>>> toSelectionCache() { 097 if (!this.valid()) { 098 throw new IllegalStateException("not valid"); 099 } 100 final Map<AttributedType, List<Bean<?>>> m = newHashMap(this.dependencyResolutions.size()); // too big but whatever 101 for (final DependencyResolution r : this.dependencyResolutions) { 102 m.putIfAbsent(r.attributedType(), r.beans()); 103 } 104 return m::computeIfAbsent; // m is mutable on purpose; we can revisit if we decide that a Model is the absolute source of truth 105 } 106 107 // s would normally be typesafeFiltering and ambiguityReducing but not necessarily cached 108 @SuppressWarnings("unchecked") 109 public static Model of(final Selectable<? super AttributedType, ? extends Bean<?>> s) { 110 final Collection<? extends Aggregate> allBeans = s.select(null); 111 if (allBeans.isEmpty()) { 112 return new Model(Set.of()); 113 } 114 final Map<AttributedType, List<Bean<?>>> m2 = newHashMap(allBeans.size() * 5); // estimate; 115 final Set<DependencyResolution> dependencyResolutions = newHashSet(allBeans.size() * 5); 116 for (final Aggregate bean : allBeans) { 117 for (final AttributedElement dependency : bean.dependencies()) { 118 dependencyResolutions.add(new DependencyResolution(dependency, 119 m2.computeIfAbsent(dependency.attributedType(), 120 at -> (List<Bean<?>>)s.select(at)))); 121 } 122 } 123 return new Model(dependencyResolutions); 124 } 125 126 public static final record DependencyResolution(AttributedElement attributedElement, List<Bean<?>> beans) { 127 128 public DependencyResolution { 129 requireNonNull(attributedElement, "attributedElement"); 130 beans = beans == null ? List.of() : List.copyOf(beans); 131 } 132 133 public final AttributedType attributedType() { 134 return this.attributedElement.attributedType(); 135 } 136 137 public final boolean ambiguous() { 138 return this.beans().size() > 1; 139 } 140 141 public final boolean unsatisfied() { 142 return this.beans().isEmpty(); 143 } 144 145 } 146 147}