001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.context.index; 018 019import java.util.Collections; 020import java.util.List; 021import java.util.Properties; 022import java.util.Set; 023import java.util.stream.Collectors; 024 025import org.springframework.util.AntPathMatcher; 026import org.springframework.util.ClassUtils; 027import org.springframework.util.LinkedMultiValueMap; 028import org.springframework.util.MultiValueMap; 029 030/** 031 * Provide access to the candidates that are defined in {@code META-INF/spring.components}. 032 * 033 * <p>An arbitrary number of stereotypes can be registered (and queried) on the index: a 034 * typical example is the fully qualified name of an annotation that flags the class for 035 * a certain use case. The following call returns all the {@code @Component} 036 * <b>candidate</b> types for the {@code com.example} package (and its sub-packages): 037 * <pre class="code"> 038 * Set<String> candidates = index.getCandidateTypes( 039 * "com.example", "org.springframework.stereotype.Component"); 040 * </pre> 041 * 042 * <p>The {@code type} is usually the fully qualified name of a class, though this is 043 * not a rule. Similarly, the {@code stereotype} is usually the fully qualified name of 044 * a target type but it can be any marker really. 045 * 046 * @author Stephane Nicoll 047 * @since 5.0 048 */ 049public class CandidateComponentsIndex { 050 051 private static final AntPathMatcher pathMatcher = new AntPathMatcher("."); 052 053 private final MultiValueMap<String, Entry> index; 054 055 056 CandidateComponentsIndex(List<Properties> content) { 057 this.index = parseIndex(content); 058 } 059 060 private static MultiValueMap<String, Entry> parseIndex(List<Properties> content) { 061 MultiValueMap<String, Entry> index = new LinkedMultiValueMap<>(); 062 for (Properties entry : content) { 063 entry.forEach((type, values) -> { 064 String[] stereotypes = ((String) values).split(","); 065 for (String stereotype : stereotypes) { 066 index.add(stereotype, new Entry((String) type)); 067 } 068 }); 069 } 070 return index; 071 } 072 073 074 /** 075 * Return the candidate types that are associated with the specified stereotype. 076 * @param basePackage the package to check for candidates 077 * @param stereotype the stereotype to use 078 * @return the candidate types associated with the specified {@code stereotype} 079 * or an empty set if none has been found for the specified {@code basePackage} 080 */ 081 public Set<String> getCandidateTypes(String basePackage, String stereotype) { 082 List<Entry> candidates = this.index.get(stereotype); 083 if (candidates != null) { 084 return candidates.parallelStream() 085 .filter(t -> t.match(basePackage)) 086 .map(t -> t.type) 087 .collect(Collectors.toSet()); 088 } 089 return Collections.emptySet(); 090 } 091 092 093 private static class Entry { 094 095 private final String type; 096 097 private final String packageName; 098 099 Entry(String type) { 100 this.type = type; 101 this.packageName = ClassUtils.getPackageName(type); 102 } 103 104 public boolean match(String basePackage) { 105 if (pathMatcher.isPattern(basePackage)) { 106 return pathMatcher.match(basePackage, this.packageName); 107 } 108 else { 109 return this.type.startsWith(basePackage); 110 } 111 } 112 } 113 114}