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