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.reflect.parameterenricher; 017 018import static java.util.Collections.emptyMap; 019import static java.util.Collections.singletonList; 020import static java.util.Collections.singletonMap; 021import static java.util.Locale.ENGLISH; 022import static java.util.function.Function.identity; 023import static java.util.stream.Collectors.joining; 024import static java.util.stream.Collectors.toList; 025import static java.util.stream.Collectors.toMap; 026 027import java.lang.annotation.Annotation; 028import java.lang.reflect.Array; 029import java.lang.reflect.InvocationTargetException; 030import java.lang.reflect.Method; 031import java.lang.reflect.Type; 032import java.time.LocalDate; 033import java.time.LocalDateTime; 034import java.time.LocalTime; 035import java.time.ZonedDateTime; 036import java.util.Collection; 037import java.util.HashMap; 038import java.util.Map; 039import java.util.function.Function; 040import java.util.stream.Stream; 041 042import org.talend.sdk.component.api.configuration.ui.layout.GridLayout; 043import org.talend.sdk.component.api.configuration.ui.layout.GridLayouts; 044import org.talend.sdk.component.api.configuration.ui.meta.Ui; 045import org.talend.sdk.component.api.configuration.ui.widget.DateTime; 046 047import lombok.Data; 048 049public class UiParameterEnricher extends BaseParameterEnricher { 050 051 public static final String META_PREFIX = "tcomp::ui::"; 052 053 @Override 054 public Map<Type, Collection<Annotation>> getImplicitAnnotationForTypes() { 055 final Collection<Annotation> annotations = singletonList(new DateTimeAnnotation()); 056 return Stream 057 .<Type> of(ZonedDateTime.class, LocalDateTime.class, LocalDate.class, LocalTime.class) 058 .collect(toMap(identity(), it -> annotations)); 059 } 060 061 @Override 062 public Map<String, String> onParameterAnnotation(final String parameterName, final Type parameterType, 063 final Annotation annotation) { 064 final Ui condition = annotation.annotationType().getAnnotation(Ui.class); 065 if (condition != null) { 066 final String prefix = META_PREFIX + annotation.annotationType().getSimpleName().toLowerCase(ENGLISH) + "::"; 067 if (GridLayouts.class == annotation.annotationType()) { 068 return Stream 069 .of(GridLayouts.class.cast(annotation).value()) 070 .flatMap(a -> toConfig(a, prefix.substring(0, prefix.length() - 3) + "::").entrySet().stream()) 071 .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); 072 } 073 if (DateTime.class == annotation.annotationType()) { 074 final String key = META_PREFIX + "datetime"; 075 final DateTime dateTime = DateTime.class.cast(annotation); 076 final Map<String, String> dtmap = new HashMap<>(); 077 if (parameterType == LocalTime.class) { 078 dtmap.put(key, "time"); 079 dtmap.put(key + "::useSeconds", String.valueOf(dateTime.useSeconds())); 080 return dtmap; 081 } 082 if (parameterType == LocalDate.class) { 083 dtmap.put(key, "date"); 084 dtmap.put(key + "::dateFormat", String.valueOf(dateTime.dateFormat())); 085 return dtmap; 086 } 087 if (parameterType == LocalDateTime.class) { 088 dtmap.put(key, "datetime"); 089 dtmap.put(key + "::dateFormat", String.valueOf(dateTime.dateFormat())); 090 dtmap.put(key + "::useSeconds", String.valueOf(dateTime.useSeconds())); 091 dtmap.put(key + "::useUTC", String.valueOf(dateTime.useUTC())); 092 return dtmap; 093 } 094 if (parameterType == ZonedDateTime.class || parameterType == Object.class /* unsafe */) { 095 dtmap.put(key, "zoneddatetime"); 096 dtmap.put(key + "::dateFormat", String.valueOf(dateTime.dateFormat())); 097 dtmap.put(key + "::useSeconds", String.valueOf(dateTime.useSeconds())); 098 dtmap.put(key + "::useUTC", String.valueOf(dateTime.useUTC())); 099 return dtmap; 100 } 101 throw new IllegalArgumentException( 102 "Unsupported type for @DateTime option: " + parameterType + " on " + parameterName); 103 } 104 return toConfig(annotation, prefix); 105 } 106 return emptyMap(); 107 } 108 109 private Map<String, String> toConfig(final Annotation annotation, final String prefix) { 110 if (GridLayout.class == annotation.annotationType()) { 111 final GridLayout layout = GridLayout.class.cast(annotation); 112 return Stream 113 .of(layout.names()) 114 .flatMap(name -> Stream 115 .of(annotation.annotationType().getMethods()) 116 .filter(m -> m.getDeclaringClass() == annotation.annotationType() 117 && !"names".equals(m.getName())) 118 .collect(toMap(m -> prefix + name + "::" + m.getName(), 119 m -> toString(annotation, m, invoke -> { 120 if (invoke.getClass().isArray()) { 121 final Class<?> component = invoke.getClass().getComponentType(); 122 if (!Annotation.class.isAssignableFrom(component)) { 123 return null; 124 } 125 final int length = Array.getLength(invoke); 126 if (length == 0) { 127 return ""; 128 } 129 final Collection<Method> mtds = Stream 130 .of(component.getMethods()) 131 .filter(mtd -> mtd.getDeclaringClass() == component 132 && "value".equals(mtd.getName())) 133 .collect(toList()); 134 final StringBuilder builder = new StringBuilder(""); 135 for (int i = 0; i < length; i++) { 136 final Object annot = Array.get(invoke, i); 137 mtds 138 .forEach(p -> builder 139 .append(toString(Annotation.class.cast(annot), p, 140 o -> null))); 141 if (i + 1 < length) { 142 builder.append('|'); 143 } 144 } 145 return builder.toString(); 146 } 147 return null; 148 }))) 149 .entrySet() 150 .stream()) 151 .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); 152 } 153 154 final Map<String, String> config = Stream 155 .of(annotation.annotationType().getMethods()) 156 .filter(m -> m.getDeclaringClass() == annotation.annotationType()) 157 .collect(toMap(m -> prefix + m.getName(), m -> toString(annotation, m, invoke -> null))); 158 return config.isEmpty() ? singletonMap(prefix.substring(0, prefix.length() - "::".length()), "true") : config; 159 } 160 161 private String toString(final Annotation annotation, final Method m, 162 final Function<Object, String> customConversions) { 163 try { 164 final Object invoke = m.invoke(annotation); 165 final String custom = customConversions.apply(invoke); 166 if (custom != null) { 167 return custom; 168 } 169 if (String.class.isInstance(invoke)) { 170 final String string = String.valueOf(invoke); 171 if (string.startsWith("local_configuration:")) { 172 return getContext() 173 .map(context -> context 174 .getConfiguration() 175 .get(string.substring("local_configuration:".length()))) 176 .orElse(string); 177 } 178 return string; 179 } 180 if (Class.class.isInstance(invoke)) { 181 return Class.class.cast(invoke).getSimpleName().toLowerCase(ENGLISH); 182 } 183 if (String[].class.isInstance(invoke)) { 184 return Stream.of(String[].class.cast(invoke)).collect(joining(",")); 185 } 186 return String.valueOf(invoke); 187 } catch (final InvocationTargetException | IllegalAccessException e) { 188 throw new IllegalStateException(e); 189 } 190 } 191 192 @Data 193 private static class DateTimeAnnotation implements DateTime { 194 195 public String dateFormat = "YYYY/MM/DD"; 196 197 boolean useSeconds = true; 198 199 boolean useUTC = true; 200 201 @Override 202 public Class<? extends Annotation> annotationType() { 203 return DateTime.class; 204 } 205 206 @Override 207 public String dateFormat() { 208 return dateFormat; 209 } 210 211 @Override 212 public boolean useSeconds() { 213 return useSeconds; 214 } 215 216 @Override 217 public boolean useUTC() { 218 return useUTC; 219 } 220 } 221}