001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.eclipse.aether.internal.impl; 020 021import java.util.ArrayList; 022import java.util.Collections; 023import java.util.List; 024import java.util.Map; 025import java.util.function.Function; 026 027import org.eclipse.aether.ConfigurationProperties; 028import org.eclipse.aether.Keys; 029import org.eclipse.aether.RepositorySystemSession; 030import org.eclipse.aether.util.ConfigUtils; 031 032/** 033 * Helps to sort pluggable components by their priority. 034 * 035 * @param <T> The component type. 036 */ 037public final class PrioritizedComponents<T> { 038 /** 039 * Reuses or creates and caches (if session is equipped with cache, and it does not contain it yet) 040 * prioritized components under certain key into session cache. Same session is used to configure prioritized 041 * components, so priority sorted components during session are immutable and reusable (if {@code components} 042 * component map is unchanged). 043 * <p> 044 * The {@code components} are expected to be Sisu injected {@link Map}-like dynamic component maps. There is a 045 * simple "change detection" in place, as injected maps are dynamic, they are atomically expanded or contracted 046 * as components are dynamically discovered or unloaded. 047 * 048 * @since 2.0.0 049 */ 050 @SuppressWarnings("unchecked") 051 public static <C> PrioritizedComponents<C> reuseOrCreate( 052 RepositorySystemSession session, 053 Class<C> discriminator, 054 Map<String, C> components, 055 Function<C, Float> priorityFunction) { 056 boolean cached = ConfigUtils.getBoolean( 057 session, ConfigurationProperties.DEFAULT_CACHED_PRIORITIES, ConfigurationProperties.CACHED_PRIORITIES); 058 if (cached && session.getCache() != null) { 059 return (PrioritizedComponents<C>) session.getCache() 060 .computeIfAbsent( 061 session, 062 Keys.of( 063 PrioritizedComponents.class, 064 discriminator, 065 "pc-" + Integer.toHexString(components.hashCode())), 066 () -> create(session, components, priorityFunction)); 067 } else { 068 return create(session, components, priorityFunction); 069 } 070 } 071 072 private static <C> PrioritizedComponents<C> create( 073 RepositorySystemSession session, Map<String, C> components, Function<C, Float> priorityFunction) { 074 PrioritizedComponents<C> newInstance = new PrioritizedComponents<>(session); 075 components.values().forEach(c -> newInstance.add(c, priorityFunction.apply(c))); 076 return newInstance; 077 } 078 079 private static final String FACTORY_SUFFIX = "Factory"; 080 081 private final Map<?, ?> configProps; 082 083 private final boolean useInsertionOrder; 084 085 private final List<PrioritizedComponent<T>> components; 086 087 private int firstDisabled; 088 089 PrioritizedComponents(RepositorySystemSession session) { 090 this(session.getConfigProperties()); 091 } 092 093 PrioritizedComponents(Map<?, ?> configurationProperties) { 094 configProps = configurationProperties; 095 useInsertionOrder = ConfigUtils.getBoolean( 096 configProps, 097 ConfigurationProperties.DEFAULT_IMPLICIT_PRIORITIES, 098 ConfigurationProperties.IMPLICIT_PRIORITIES); 099 components = new ArrayList<>(); 100 firstDisabled = 0; 101 } 102 103 public void add(T component, float priority) { 104 Class<?> type = getImplClass(component); 105 int index = components.size(); 106 priority = useInsertionOrder ? -index : ConfigUtils.getFloat(configProps, priority, getConfigKeys(type)); 107 PrioritizedComponent<T> pc = new PrioritizedComponent<>(component, type, priority, index); 108 109 if (!useInsertionOrder) { 110 index = Collections.binarySearch(components, pc); 111 if (index < 0) { 112 index = -index - 1; 113 } else { 114 index++; 115 } 116 } 117 components.add(index, pc); 118 119 if (index <= firstDisabled && !pc.isDisabled()) { 120 firstDisabled++; 121 } 122 } 123 124 private static Class<?> getImplClass(Object component) { 125 Class<?> type = component.getClass(); 126 // detect and ignore CGLIB-based proxy classes employed by Guice for AOP (cf. BytecodeGen.newEnhancer) 127 int idx = type.getName().indexOf("$$"); 128 if (idx >= 0) { 129 Class<?> base = type.getSuperclass(); 130 if (base != null && idx == base.getName().length() && type.getName().startsWith(base.getName())) { 131 type = base; 132 } 133 } 134 return type; 135 } 136 137 static String[] getConfigKeys(Class<?> type) { 138 List<String> keys = new ArrayList<>(); 139 keys.add(ConfigurationProperties.PREFIX_PRIORITY + type.getName()); 140 String sn = type.getSimpleName(); 141 keys.add(ConfigurationProperties.PREFIX_PRIORITY + sn); 142 if (sn.endsWith(FACTORY_SUFFIX)) { 143 keys.add(ConfigurationProperties.PREFIX_PRIORITY + sn.substring(0, sn.length() - FACTORY_SUFFIX.length())); 144 } 145 return keys.toArray(new String[0]); 146 } 147 148 public boolean isEmpty() { 149 return components.isEmpty(); 150 } 151 152 public List<PrioritizedComponent<T>> getAll() { 153 return components; 154 } 155 156 public List<PrioritizedComponent<T>> getEnabled() { 157 return components.subList(0, firstDisabled); 158 } 159 160 public void list(StringBuilder buffer) { 161 int i = 0; 162 for (PrioritizedComponent<?> component : components) { 163 if (i++ > 0) { 164 buffer.append(", "); 165 } 166 buffer.append(component.getType().getSimpleName()); 167 if (component.isDisabled()) { 168 buffer.append(" (disabled)"); 169 } 170 } 171 } 172 173 @Override 174 public String toString() { 175 return components.toString(); 176 } 177}