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