001/**
002 * Copyright (C) 2006-2024 Talend Inc. - www.talend.com
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.talend.sdk.component.runtime.manager.reflect;
017
018import static java.util.Comparator.comparing;
019import static java.util.Optional.of;
020import static java.util.Optional.ofNullable;
021import static org.talend.sdk.component.api.component.Icon.IconType.CUSTOM;
022
023import java.lang.annotation.Annotation;
024import java.lang.reflect.AnnotatedElement;
025import java.lang.reflect.InvocationTargetException;
026import java.util.Optional;
027import java.util.stream.Stream;
028
029import org.talend.sdk.component.api.component.Icon;
030
031public class IconFinder {
032
033    public String findIcon(final AnnotatedElement type) {
034        return findDirectIcon(type).orElseGet(() -> findIndirectIcon(type).orElse("default"));
035    }
036
037    public Optional<String> findIndirectIcon(final AnnotatedElement type) {
038        // first try an annotation decorated with @Icon
039        return ofNullable(findMetaIconAnnotation(type)
040                .map(this::getMetaIcon)
041                .orElseGet(() -> findImplicitIcon(type).orElse(null)));
042    }
043
044    private Optional<Annotation> findMetaIconAnnotation(final AnnotatedElement type) {
045        return Stream
046                .of(type.getAnnotations())
047                .filter(this::isMetaIcon)
048                .min(comparing(it -> it.annotationType().getName()));
049    }
050
051    private boolean isMetaIcon(final Annotation it) {
052        return it.annotationType().isAnnotationPresent(Icon.class) && hasMethod(it.annotationType(), "value");
053    }
054
055    private Optional<String> findImplicitIcon(final AnnotatedElement type) {
056        return findImplicitIconAnnotation(type).map(it -> String.valueOf(invoke(it, it.annotationType(), "value")));
057    }
058
059    private Optional<Annotation> findImplicitIconAnnotation(final AnnotatedElement type) {
060        return Stream
061                .of(type.getAnnotations())
062                .filter(it -> it.annotationType().getSimpleName().endsWith("Icon")
063                        && hasMethod(it.annotationType(), "value"))
064                .findFirst();
065    }
066
067    private String getMetaIcon(final Annotation it) {
068        // extract type and value (by convention), type can be custom to use value
069        if (hasMethod(it.annotationType(), "type")) {
070            final Object enumValue = invoke(it, it.annotationType(), "type");
071            if (!"custom".equalsIgnoreCase(String.valueOf(enumValue))) {
072                return getEnumKey(enumValue).orElseGet(() -> String.valueOf(invoke(it, it.annotationType(), "value")));
073            }
074        }
075        final Object value = invoke(it, it.annotationType(), "value");
076        if (value.getClass().isEnum()) {
077            return getEnumKey(value).orElseGet(() -> String.valueOf(value));
078        }
079        return String.valueOf(value);
080    }
081
082    private Optional<String> getEnumKey(final Object enumValue) {
083        return Stream
084                .of("getKey", "getValue", "name")
085                .filter(getter -> hasMethod(enumValue.getClass(), getter))
086                .map(name -> String.valueOf(invoke(enumValue, enumValue.getClass(), name)))
087                .findFirst();
088    }
089
090    private boolean hasMethod(final Class<?> clazz, final String method) {
091        try {
092            return clazz.getMethod(method) != null;
093        } catch (final NoSuchMethodException e) {
094            return false;
095        }
096    }
097
098    private Object invoke(final Object instance, final Class<?> type, final String method) {
099        try {
100            return type.getMethod(method).invoke(instance);
101        } catch (final IllegalAccessException | NoSuchMethodException e) {
102            throw new IllegalStateException(e);
103        } catch (final InvocationTargetException e) {
104            throw new IllegalStateException(e.getTargetException());
105        }
106    }
107
108    public Optional<String> findDirectIcon(final AnnotatedElement type) {
109        return ofNullable(type.getAnnotation(Icon.class))
110                .map(i -> i.value() == Icon.IconType.CUSTOM ? of(i.custom()).filter(s -> !s.isEmpty()).orElse("default")
111                        : i.value().getKey());
112    }
113
114    public boolean isCustom(final Annotation icon) {
115        if (Icon.class == icon.annotationType()) {
116            return Icon.class.cast(icon).value() == CUSTOM;
117        }
118        if (hasMethod(icon.annotationType(), "type")) {
119            return "custom".equalsIgnoreCase(String.valueOf(invoke(icon, icon.annotationType(), "type")));
120        }
121        return false;
122    }
123
124    public Annotation extractIcon(final AnnotatedElement annotatedElement) {
125        return ofNullable(annotatedElement.getAnnotation(Icon.class))
126                .map(Annotation.class::cast)
127                .orElseGet(() -> findMetaIconAnnotation(annotatedElement)
128                        .orElseGet(() -> findImplicitIconAnnotation(annotatedElement).orElse(null)));
129    }
130}