001/**
002 * Copyright (C) 2006-2025 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;
017
018import static java.util.Arrays.asList;
019import static java.util.Optional.empty;
020import static java.util.Optional.of;
021
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.List;
025import java.util.Locale;
026import java.util.Map;
027import java.util.MissingResourceException;
028import java.util.Objects;
029import java.util.Optional;
030import java.util.ResourceBundle;
031import java.util.concurrent.ConcurrentHashMap;
032import java.util.concurrent.ConcurrentMap;
033import java.util.stream.Stream;
034
035import org.talend.sdk.component.runtime.internationalization.ParameterBundle;
036
037import lombok.Data;
038import lombok.ToString;
039import lombok.extern.slf4j.Slf4j;
040
041@Data
042@Slf4j
043@ToString(of = { "path", "name", "type", "metadata" })
044public class ParameterMeta {
045
046    private static final ParameterBundle NO_PARAMETER_BUNDLE = new ParameterBundle(null, null) {
047
048        @Override
049        protected Optional<String> readValue(final String key) {
050            return empty();
051        }
052
053        @Override
054        protected Optional<String> readRawValue(final String key) {
055            return empty();
056        }
057
058        @Override
059        public Optional<String> displayName(final ParameterBundle parent) {
060            return empty();
061        }
062
063        @Override
064        public Optional<String> placeholder(final ParameterBundle parent) {
065            return empty();
066        }
067    };
068
069    private final Source source;
070
071    private final java.lang.reflect.Type javaType;
072
073    private final Type type;
074
075    private final String path;
076
077    private final String name;
078
079    private final String[] i18nPackages; // fallback when the type is not sufficient (java.* types)
080
081    private final List<ParameterMeta> nestedParameters;
082
083    private final Collection<String> proposals;
084
085    private final Map<String, String> metadata;
086
087    private final boolean logMissingResourceBundle;
088
089    private final ConcurrentMap<Locale, ParameterBundle> bundles = new ConcurrentHashMap<>();
090
091    public ParameterBundle findBundle(final ClassLoader loader, final Locale locale) {
092        final Class<?> type = of(javaType)
093                .filter(Class.class::isInstance)
094                .map(Class.class::cast)
095                .filter(c -> !c.getName().startsWith("java.") && !c.isPrimitive())
096                .orElse(null);
097        return bundles.computeIfAbsent(locale, l -> {
098            try {
099                final ResourceBundle[] bundles =
100                        (i18nPackages != null ? Stream.of(i18nPackages) : Stream.<String> empty())
101                                .filter(Objects::nonNull)
102                                .filter(s -> !s.isEmpty())
103                                .distinct()
104                                .map(p -> p + "." + "Messages")
105                                .map(n -> {
106                                    try {
107                                        return ResourceBundle.getBundle(n, locale, loader);
108                                    } catch (final MissingResourceException mre) {
109                                        return null;
110                                    }
111                                })
112                                .filter(Objects::nonNull)
113                                .toArray(ResourceBundle[]::new);
114                if (bundles.length == 0) {
115                    if (logMissingResourceBundle) {
116                        log.warn(noBundleMessage());
117                    }
118                    return NO_PARAMETER_BUNDLE;
119                }
120
121                final Collection<String> fallbacks = new ArrayList<>(2);
122                final Class<?> declaringClass = source == null ? null : source.declaringClass();
123                if (declaringClass != null && !declaringClass.getName().startsWith("java")) {
124                    final String sourceName = source.name();
125                    fallbacks.add(declaringClass.getName() + '.' + sourceName);
126                    if (declaringClass.getEnclosingClass() != null) {
127                        fallbacks
128                                .add(declaringClass.getEnclosingClass().getSimpleName() + '$'
129                                        + declaringClass.getSimpleName() + '.' + sourceName);
130                    }
131                    fallbacks.add(declaringClass.getSimpleName() + '.' + sourceName);
132                }
133                if (type != null) {
134                    fallbacks.add(type.getName() + '.' + name);
135                    if (type.getEnclosingClass() != null) {
136                        fallbacks
137                                .add(type.getEnclosingClass().getSimpleName() + '$' + type.getSimpleName() + '.'
138                                        + name);
139                    }
140                    fallbacks.add(type.getSimpleName() + '.' + name);
141                }
142                return new ParameterBundle(bundles, path + '.', fallbacks.toArray(new String[fallbacks.size()]));
143            } catch (final MissingResourceException mre) {
144                if (logMissingResourceBundle) {
145                    log.warn(noBundleMessage());
146                }
147                log.debug(mre.getMessage(), mre);
148                return NO_PARAMETER_BUNDLE;
149            }
150        });
151    }
152
153    private String noBundleMessage() {
154        return (i18nPackages == null ? "No bundle " : "No bundle in " + asList(i18nPackages)) + " (" + path
155                + "), means the display names will be the technical names";
156    }
157
158    public enum Type {
159        OBJECT,
160        ARRAY,
161        BOOLEAN,
162        STRING,
163        NUMBER,
164        ENUM
165    }
166
167    public interface Source {
168
169        String name();
170
171        Class<?> declaringClass();
172    }
173}