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