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