View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.internal.impl;
20  
21  import java.util.ArrayList;
22  import java.util.Collections;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.function.Function;
26  
27  import org.eclipse.aether.ConfigurationProperties;
28  import org.eclipse.aether.Keys;
29  import org.eclipse.aether.RepositorySystemSession;
30  import org.eclipse.aether.util.ConfigUtils;
31  
32  /**
33   * Helps to sort pluggable components by their priority.
34   *
35   * @param <T> The component type.
36   */
37  public final class PrioritizedComponents<T> {
38      /**
39       * Reuses or creates and caches (if session is equipped with cache, and it does not contain it yet)
40       * prioritized components under certain key into session cache. Same session is used to configure prioritized
41       * components, so priority sorted components during session are immutable and reusable (if {@code components}
42       * component map is unchanged).
43       * <p>
44       * The {@code components} are expected to be Sisu injected {@link Map}-like dynamic component maps. There is a
45       * simple "change detection" in place, as injected maps are dynamic, they are atomically expanded or contracted
46       * as components are dynamically discovered or unloaded.
47       *
48       * @since 2.0.0
49       */
50      @SuppressWarnings("unchecked")
51      public static <C> PrioritizedComponents<C> reuseOrCreate(
52              RepositorySystemSession session,
53              Class<C> discriminator,
54              Map<String, C> components,
55              Function<C, Float> priorityFunction) {
56          boolean cached = ConfigUtils.getBoolean(
57                  session, ConfigurationProperties.DEFAULT_CACHED_PRIORITIES, ConfigurationProperties.CACHED_PRIORITIES);
58          if (cached && session.getCache() != null) {
59              return (PrioritizedComponents<C>) session.getCache()
60                      .computeIfAbsent(
61                              session,
62                              Keys.of(
63                                      PrioritizedComponents.class,
64                                      discriminator,
65                                      "pc-" + Integer.toHexString(components.hashCode())),
66                              () -> create(session, components, priorityFunction));
67          } else {
68              return create(session, components, priorityFunction);
69          }
70      }
71  
72      private static <C> PrioritizedComponents<C> create(
73              RepositorySystemSession session, Map<String, C> components, Function<C, Float> priorityFunction) {
74          PrioritizedComponents<C> newInstance = new PrioritizedComponents<>(session);
75          components.values().forEach(c -> newInstance.add(c, priorityFunction.apply(c)));
76          return newInstance;
77      }
78  
79      private static final String FACTORY_SUFFIX = "Factory";
80  
81      private final Map<?, ?> configProps;
82  
83      private final boolean useInsertionOrder;
84  
85      private final List<PrioritizedComponent<T>> components;
86  
87      private int firstDisabled;
88  
89      PrioritizedComponents(RepositorySystemSession session) {
90          this(session.getConfigProperties());
91      }
92  
93      PrioritizedComponents(Map<?, ?> configurationProperties) {
94          configProps = configurationProperties;
95          useInsertionOrder = ConfigUtils.getBoolean(
96                  configProps,
97                  ConfigurationProperties.DEFAULT_IMPLICIT_PRIORITIES,
98                  ConfigurationProperties.IMPLICIT_PRIORITIES);
99          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 }