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.RepositorySystemSession; 029import org.eclipse.aether.util.ConfigUtils; 030 031/** 032 * Helps to sort pluggable components by their priority. 033 * 034 * @param <T> The component type. 035 */ 036public final class PrioritizedComponents<T> { 037 /** 038 * Reuses or creates and caches (if session is equipped with cache, and it does not contain it yet) 039 * prioritized components under certain key into session cache. Same session is used to configure prioritized 040 * components, so priority sorted components during session are immutable and reusable (if {@code components} 041 * component map is unchanged). 042 * <p> 043 * The {@code components} are expected to be Sisu injected {@link Map}-like dynamic component maps. There is a 044 * simple "change detection" in place, as injected maps are dynamic, they are atomically expanded or contracted 045 * as components are dynamically discovered or unloaded. 046 * 047 * @since 2.0.0 048 */ 049 @SuppressWarnings("unchecked") 050 public static <C> PrioritizedComponents<C> reuseOrCreate( 051 RepositorySystemSession session, 052 Class<C> discriminator, 053 Map<String, C> components, 054 Function<C, Float> priorityFunction) { 055 boolean cached = ConfigUtils.getBoolean( 056 session, ConfigurationProperties.DEFAULT_CACHED_PRIORITIES, ConfigurationProperties.CACHED_PRIORITIES); 057 if (cached && session.getCache() != null) { 058 String key = PrioritizedComponents.class.getName() + ".pc." + discriminator.getName() 059 + Integer.toHexString(components.hashCode()); 060 return (PrioritizedComponents<C>) session.getCache() 061 .computeIfAbsent(session, key, () -> create(session, components, priorityFunction)); 062 } else { 063 return create(session, components, priorityFunction); 064 } 065 } 066 067 private static <C> PrioritizedComponents<C> create( 068 RepositorySystemSession session, Map<String, C> components, Function<C, Float> priorityFunction) { 069 PrioritizedComponents<C> newInstance = new PrioritizedComponents<>(session); 070 components.values().forEach(c -> newInstance.add(c, priorityFunction.apply(c))); 071 return newInstance; 072 } 073 074 private static final String FACTORY_SUFFIX = "Factory"; 075 076 private final Map<?, ?> configProps; 077 078 private final boolean useInsertionOrder; 079 080 private final List<PrioritizedComponent<T>> components; 081 082 private int firstDisabled; 083 084 PrioritizedComponents(RepositorySystemSession session) { 085 this(session.getConfigProperties()); 086 } 087 088 PrioritizedComponents(Map<?, ?> configurationProperties) { 089 configProps = configurationProperties; 090 useInsertionOrder = ConfigUtils.getBoolean( 091 configProps, 092 ConfigurationProperties.DEFAULT_IMPLICIT_PRIORITIES, 093 ConfigurationProperties.IMPLICIT_PRIORITIES); 094 components = new ArrayList<>(); 095 firstDisabled = 0; 096 } 097 098 public void add(T component, float priority) { 099 Class<?> type = getImplClass(component); 100 int index = components.size(); 101 priority = useInsertionOrder ? -index : ConfigUtils.getFloat(configProps, priority, getConfigKeys(type)); 102 PrioritizedComponent<T> pc = new PrioritizedComponent<>(component, type, priority, index); 103 104 if (!useInsertionOrder) { 105 index = Collections.binarySearch(components, pc); 106 if (index < 0) { 107 index = -index - 1; 108 } else { 109 index++; 110 } 111 } 112 components.add(index, pc); 113 114 if (index <= firstDisabled && !pc.isDisabled()) { 115 firstDisabled++; 116 } 117 } 118 119 private static Class<?> getImplClass(Object component) { 120 Class<?> type = component.getClass(); 121 // detect and ignore CGLIB-based proxy classes employed by Guice for AOP (cf. BytecodeGen.newEnhancer) 122 int idx = type.getName().indexOf("$$"); 123 if (idx >= 0) { 124 Class<?> base = type.getSuperclass(); 125 if (base != null && idx == base.getName().length() && type.getName().startsWith(base.getName())) { 126 type = base; 127 } 128 } 129 return type; 130 } 131 132 static String[] getConfigKeys(Class<?> type) { 133 List<String> keys = new ArrayList<>(); 134 keys.add(ConfigurationProperties.PREFIX_PRIORITY + type.getName()); 135 String sn = type.getSimpleName(); 136 keys.add(ConfigurationProperties.PREFIX_PRIORITY + sn); 137 if (sn.endsWith(FACTORY_SUFFIX)) { 138 keys.add(ConfigurationProperties.PREFIX_PRIORITY + sn.substring(0, sn.length() - FACTORY_SUFFIX.length())); 139 } 140 return keys.toArray(new String[0]); 141 } 142 143 public boolean isEmpty() { 144 return components.isEmpty(); 145 } 146 147 public List<PrioritizedComponent<T>> getAll() { 148 return components; 149 } 150 151 public List<PrioritizedComponent<T>> getEnabled() { 152 return components.subList(0, firstDisabled); 153 } 154 155 public void list(StringBuilder buffer) { 156 int i = 0; 157 for (PrioritizedComponent<?> component : components) { 158 if (i++ > 0) { 159 buffer.append(", "); 160 } 161 buffer.append(component.getType().getSimpleName()); 162 if (component.isDisabled()) { 163 buffer.append(" (disabled)"); 164 } 165 } 166 } 167 168 @Override 169 public String toString() { 170 return components.toString(); 171 } 172}