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