001/* 002 * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclets.toolkit.util; 027 028import java.util.ArrayList; 029import java.util.Iterator; 030import java.util.Objects; 031import java.util.Optional; 032import java.util.function.BiFunction; 033import java.util.function.Function; 034 035import javax.lang.model.element.ExecutableElement; 036 037public class DocFinder { 038 039 /* 040 * A specialized, possibly stateful, function that accepts a method in the 041 * hierarchy and returns a value that controls the search or throws an 042 * exception, which terminates the search and transparently bubbles 043 * up the stack. 044 */ 045 @FunctionalInterface 046 public interface Criterion<T, X extends Throwable> { 047 Result<T> apply(ExecutableElement method) throws X; 048 } 049 050 private final Function<ExecutableElement, 051 ExecutableElement> overriddenMethodLookup; 052 private final BiFunction<ExecutableElement, ExecutableElement, 053 Iterable<ExecutableElement>> implementedMethodsLookup; 054 055 DocFinder( 056 Function<ExecutableElement, 057 ExecutableElement> overriddenMethodLookup, 058 BiFunction<ExecutableElement, ExecutableElement, 059 Iterable<ExecutableElement>> implementedMethodsLookup) { 060 this.overriddenMethodLookup = overriddenMethodLookup; 061 this.implementedMethodsLookup = implementedMethodsLookup; 062 } 063 064 @SuppressWarnings("serial") 065 public static final class NoOverriddenMethodsFound extends Exception { 066 067 // only DocFinder should instantiate this exception 068 private NoOverriddenMethodsFound() { 069 } 070 } 071 072 public <T, X extends Throwable> Result<T> search(ExecutableElement method, 073 Criterion<T, X> criterion) 074 throws X { 075 return search(method, true, criterion); 076 } 077 078 public <T, X extends Throwable> Result<T> search(ExecutableElement method, 079 boolean includeMethod, 080 Criterion<T, X> criterion) 081 throws X { 082 try { 083 return search0(method, includeMethod, false, criterion); 084 } catch (NoOverriddenMethodsFound e) { 085 // should not happen because the exception flag is unset 086 throw new AssertionError(e); 087 } 088 } 089 090 public <T, X extends Throwable> Result<T> trySearch( 091 ExecutableElement method, 092 Criterion<T, X> criterion) 093 throws NoOverriddenMethodsFound, X { 094 return search0(method, false, true, criterion); 095 } 096 097 /* 098 * Searches through the overridden methods hierarchy of the provided method. 099 * 100 * Depending on how it is instructed, the search begins from either the 101 * given 102 * method or the first method that the given method overrides. The search 103 * then applies the given criterion to methods it encounters, in the 104 * hierarchy order, until either of the following happens: 105 * 106 * - the criterion concludes the search 107 * - the criterion throws an exception 108 * - the hierarchy is exhausted 109 * 110 * If the search succeeds, the returned result is of type Conclude. 111 * Otherwise, the returned result is generally that of the most 112 * recent call to Criterion::apply. 113 * 114 * If the given method overrides no methods (i.e. hierarchy consists of the 115 * given method only) and the search is instructed to detect that, the 116 * search terminates with an exception. 117 */ 118 private <T, X extends Throwable> Result<T> search0(ExecutableElement method, 119 boolean includeMethodInSearch, 120 boolean throwExceptionIfDoesNotOverride, 121 Criterion<T, X> criterion) 122 throws NoOverriddenMethodsFound, X { 123 // if the "overrides" check is requested and does not pass, throw the 124 // exception 125 // first so that it trumps the result that the search would otherwise 126 // had 127 Iterator<ExecutableElement> methods = methodsOverriddenBy(method); 128 if (throwExceptionIfDoesNotOverride && !methods.hasNext()) { 129 throw new NoOverriddenMethodsFound(); 130 } 131 Result<T> r = includeMethodInSearch ? criterion.apply(method) 132 : Result.CONTINUE(); 133 if (!(r instanceof Result.Continue<T>)) { 134 return r; 135 } 136 while (methods.hasNext()) { 137 ExecutableElement m = methods.next(); 138 r = search0(m, true, false /* don't check for overrides */, 139 criterion); 140 if (r instanceof Result.Conclude<T>) { 141 return r; 142 } 143 } 144 return r; 145 } 146 147 // We see both overridden and implemented methods as overridden 148 // (see JLS 8.4.8.1. Overriding (by Instance Methods)) 149 private Iterator<ExecutableElement> 150 methodsOverriddenBy(ExecutableElement method) { 151 // TODO: create a lazy iterator if required 152 var list = new ArrayList<ExecutableElement>(); 153 ExecutableElement overridden = overriddenMethodLookup.apply(method); 154 if (overridden != null) { 155 list.add(overridden); 156 } 157 implementedMethodsLookup.apply(method, method).forEach(list::add); 158 return list.iterator(); 159 } 160 161 private static final Result<?> SKIP = new Skipped<>(); 162 private static final Result<?> CONTINUE = new Continued<>(); 163 164 /* 165 * Use static factory methods to get the desired result to return from 166 * Criterion. Use instanceof to check for a result type returned from 167 * a search. If a use case permits and you prefer Optional API, use 168 * the fromOptional/toOptional convenience methods to get and 169 * check for the result respectively. 170 */ 171 public sealed interface Result<T> { 172 173 sealed interface Skip<T> extends Result<T> permits Skipped { 174 } 175 176 sealed interface Continue<T> extends Result<T> permits Continued { 177 } 178 179 sealed interface Conclude<T> extends Result<T> permits Concluded { 180 181 T value(); 182 } 183 184 /* 185 * Skips the search on the part of the hierarchy above the method for 186 * which this result is returned and continues the search from that 187 * method sibling, if any. 188 */ 189 @SuppressWarnings("unchecked") 190 static <T> Result<T> SKIP() { 191 return (Result<T>) SKIP; 192 } 193 194 /* 195 * Continues the search. 196 */ 197 @SuppressWarnings("unchecked") 198 static <T> Result<T> CONTINUE() { 199 return (Result<T>) CONTINUE; 200 } 201 202 /* 203 * Concludes the search with the given result. 204 */ 205 static <T> Result<T> CONCLUDE(T value) { 206 return new Concluded<>(value); 207 } 208 209 /* 210 * Translates this Result into Optional. 211 * 212 * Convenience method. Call on the result of a search if you are only 213 * interested in whether the search succeeded or failed and you 214 * prefer the Optional API. 215 */ 216 default Optional<T> toOptional() { 217 return Optional.empty(); 218 } 219 220 /* 221 * Translates the given Optional into a binary decision whether to 222 * conclude the search or continue it. 223 * 224 * Convenience method. Use in Criterion that can easily provide 225 * suitable Optional. Don't use if Criterion needs to skip. 226 */ 227 static <T> Result<T> fromOptional(Optional<T> optional) { 228 return optional.map(Result::CONCLUDE).orElseGet(Result::CONTINUE); 229 } 230 } 231 232 // Note: we hide records behind interfaces, as implementation detail. 233 // We don't directly implement Result with these records because it 234 // would require more exposure and commitment than is desired. For 235 // example, there would need to be public constructors, which 236 // would circumvent static factory methods. 237 238 private record Skipped<T>() implements DocFinder.Result.Skip<T> { 239 } 240 241 private record Continued<T>() implements DocFinder.Result.Continue<T> { 242 } 243 244 private record Concluded<T>(T value) 245 implements DocFinder.Result.Conclude<T> { 246 247 Concluded { 248 Objects.requireNonNull(value); 249 } 250 251 @Override 252 public Optional<T> toOptional() { 253 return Optional.of(value); 254 } 255 } 256}