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.Keys;
029import org.eclipse.aether.RepositorySystemSession;
030import org.eclipse.aether.util.ConfigUtils;
031
032/**
033 * Helps to sort pluggable components by their priority.
034 *
035 * @param <T> The component type.
036 */
037public final class PrioritizedComponents<T> {
038    /**
039     * Reuses or creates and caches (if session is equipped with cache, and it does not contain it yet)
040     * prioritized components under certain key into session cache. Same session is used to configure prioritized
041     * components, so priority sorted components during session are immutable and reusable (if {@code components}
042     * component map is unchanged).
043     * <p>
044     * The {@code components} are expected to be Sisu injected {@link Map}-like dynamic component maps. There is a
045     * simple "change detection" in place, as injected maps are dynamic, they are atomically expanded or contracted
046     * as components are dynamically discovered or unloaded.
047     *
048     * @since 2.0.0
049     */
050    @SuppressWarnings("unchecked")
051    public static <C> PrioritizedComponents<C> reuseOrCreate(
052            RepositorySystemSession session,
053            Class<C> discriminator,
054            Map<String, C> components,
055            Function<C, Float> priorityFunction) {
056        boolean cached = ConfigUtils.getBoolean(
057                session, ConfigurationProperties.DEFAULT_CACHED_PRIORITIES, ConfigurationProperties.CACHED_PRIORITIES);
058        if (cached && session.getCache() != null) {
059            return (PrioritizedComponents<C>) session.getCache()
060                    .computeIfAbsent(
061                            session,
062                            Keys.of(
063                                    PrioritizedComponents.class,
064                                    discriminator,
065                                    "pc-" + Integer.toHexString(components.hashCode())),
066                            () -> create(session, components, priorityFunction));
067        } else {
068            return create(session, components, priorityFunction);
069        }
070    }
071
072    private static <C> PrioritizedComponents<C> create(
073            RepositorySystemSession session, Map<String, C> components, Function<C, Float> priorityFunction) {
074        PrioritizedComponents<C> newInstance = new PrioritizedComponents<>(session);
075        components.values().forEach(c -> newInstance.add(c, priorityFunction.apply(c)));
076        return newInstance;
077    }
078
079    private static final String FACTORY_SUFFIX = "Factory";
080
081    private final Map<?, ?> configProps;
082
083    private final boolean useInsertionOrder;
084
085    private final List<PrioritizedComponent<T>> components;
086
087    private int firstDisabled;
088
089    PrioritizedComponents(RepositorySystemSession session) {
090        this(session.getConfigProperties());
091    }
092
093    PrioritizedComponents(Map<?, ?> configurationProperties) {
094        configProps = configurationProperties;
095        useInsertionOrder = ConfigUtils.getBoolean(
096                configProps,
097                ConfigurationProperties.DEFAULT_IMPLICIT_PRIORITIES,
098                ConfigurationProperties.IMPLICIT_PRIORITIES);
099        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}