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.RepositorySystemSession;
29  import org.eclipse.aether.util.ConfigUtils;
30  
31  /**
32   * Helps to sort pluggable components by their priority.
33   *
34   * @param <T> The component type.
35   */
36  public final class PrioritizedComponents<T> {
37      /**
38       * Reuses or creates and caches (if session is equipped with cache, and it does not contain it yet)
39       * prioritized components under certain key into session cache. Same session is used to configure prioritized
40       * components, so priority sorted components during session are immutable and reusable (if {@code components}
41       * component map is unchanged).
42       * <p>
43       * The {@code components} are expected to be Sisu injected {@link Map}-like dynamic component maps. There is a
44       * simple "change detection" in place, as injected maps are dynamic, they are atomically expanded or contracted
45       * as components are dynamically discovered or unloaded.
46       *
47       * @since 2.0.0
48       */
49      @SuppressWarnings("unchecked")
50      public static <C> PrioritizedComponents<C> reuseOrCreate(
51              RepositorySystemSession session,
52              Class<C> discriminator,
53              Map<String, C> components,
54              Function<C, Float> priorityFunction) {
55          boolean cached = ConfigUtils.getBoolean(
56                  session, ConfigurationProperties.DEFAULT_CACHED_PRIORITIES, ConfigurationProperties.CACHED_PRIORITIES);
57          if (cached && session.getCache() != null) {
58              String key = PrioritizedComponents.class.getName() + ".pc." + discriminator.getName()
59                      + Integer.toHexString(components.hashCode());
60              return (PrioritizedComponents<C>) session.getCache()
61                      .computeIfAbsent(session, key, () -> create(session, components, priorityFunction));
62          } else {
63              return create(session, components, priorityFunction);
64          }
65      }
66  
67      private static <C> PrioritizedComponents<C> create(
68              RepositorySystemSession session, Map<String, C> components, Function<C, Float> priorityFunction) {
69          PrioritizedComponents<C> newInstance = new PrioritizedComponents<>(session);
70          components.values().forEach(c -> newInstance.add(c, priorityFunction.apply(c)));
71          return newInstance;
72      }
73  
74      private static final String FACTORY_SUFFIX = "Factory";
75  
76      private final Map<?, ?> configProps;
77  
78      private final boolean useInsertionOrder;
79  
80      private final List<PrioritizedComponent<T>> components;
81  
82      private int firstDisabled;
83  
84      PrioritizedComponents(RepositorySystemSession session) {
85          this(session.getConfigProperties());
86      }
87  
88      PrioritizedComponents(Map<?, ?> configurationProperties) {
89          configProps = configurationProperties;
90          useInsertionOrder = ConfigUtils.getBoolean(
91                  configProps,
92                  ConfigurationProperties.DEFAULT_IMPLICIT_PRIORITIES,
93                  ConfigurationProperties.IMPLICIT_PRIORITIES);
94          components = new ArrayList<>();
95          firstDisabled = 0;
96      }
97  
98      public void add(T component, float priority) {
99          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 }