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.util; 017 018import static java.util.Collections.singleton; 019import static java.util.Collections.singletonList; 020import static java.util.Optional.ofNullable; 021import static java.util.stream.Collectors.joining; 022import static lombok.AccessLevel.PRIVATE; 023 024import java.lang.reflect.Field; 025import java.lang.reflect.InvocationTargetException; 026import java.lang.reflect.ParameterizedType; 027import java.lang.reflect.Type; 028import java.util.Collection; 029import java.util.List; 030import java.util.Set; 031import java.util.SortedSet; 032import java.util.TreeSet; 033 034import org.talend.sdk.component.runtime.manager.ParameterMeta; 035 036import lombok.AllArgsConstructor; 037import lombok.Data; 038 039public class DefaultValueInspector { 040 041 // for now we use instantiation to find the defaults assuming it will be cached 042 // but we can move it later in design module to directly read it from the bytecode 043 public Instance createDemoInstance(final Object rootInstance, final ParameterMeta param) { 044 if (rootInstance != null) { 045 final Object field = findField(rootInstance, param); 046 if (field != null) { 047 return new Instance(field, false); 048 } 049 } 050 051 final Type javaType = param.getJavaType(); 052 if (Class.class.isInstance(javaType)) { 053 return new Instance(tryCreatingObjectInstance(javaType), true); 054 } else if (ParameterizedType.class.isInstance(javaType)) { 055 final ParameterizedType pt = ParameterizedType.class.cast(javaType); 056 final Type rawType = pt.getRawType(); 057 if (Class.class.isInstance(rawType) && Collection.class.isAssignableFrom(Class.class.cast(rawType)) 058 && pt.getActualTypeArguments().length == 1 059 && Class.class.isInstance(pt.getActualTypeArguments()[0])) { 060 final Object instance = tryCreatingObjectInstance(pt.getActualTypeArguments()[0]); 061 final Class<?> collectionType = Class.class.cast(rawType); 062 if (Set.class == collectionType) { 063 return new Instance(singleton(instance), true); 064 } 065 if (SortedSet.class == collectionType) { 066 return new Instance(new TreeSet<>(singletonList(instance)), true); 067 } 068 if (List.class == collectionType || Collection.class == collectionType) { 069 return new Instance(singletonList(instance), true); 070 } 071 // todo? 072 return null; 073 } 074 } 075 return null; 076 } 077 078 public String findDefault(final Object instance, final ParameterMeta param) { 079 if (instance == null) { 080 return null; 081 } 082 final ParameterMeta.Type type = param.getType(); 083 switch (type) { 084 case OBJECT: 085 return null; 086 case ENUM: 087 return Enum.class.cast(instance).name(); 088 case STRING: 089 case NUMBER: 090 case BOOLEAN: 091 return String.valueOf(instance); 092 case ARRAY: // can be enhanced 093 if (!param.getNestedParameters().isEmpty()) { 094 return null; 095 } else if (Collection.class.isInstance(instance)) { 096 return ((Collection<Object>) instance).stream().map(String::valueOf).collect(joining(",")); 097 } else { // primitives 098 return String.valueOf(instance); 099 } 100 default: 101 throw new IllegalArgumentException("Unsupported type: " + param.getType()); 102 } 103 } 104 105 private Object findField(final Object rootInstance, final ParameterMeta param) { 106 if (param.getPath().startsWith("$") || param.getName().startsWith("$")) { // virtual param 107 return null; 108 } 109 if (Collection.class.isInstance(rootInstance)) { 110 return findCollectionField(rootInstance, param); 111 } 112 Class<?> current = rootInstance.getClass(); 113 while (current != null) { 114 try { 115 final Field declaredField = current.getDeclaredField(findName(param)); 116 if (!declaredField.isAccessible()) { 117 declaredField.setAccessible(true); 118 } 119 return declaredField.get(rootInstance); 120 } catch (final IllegalAccessException | NoSuchFieldException e) { 121 // next 122 } 123 current = current.getSuperclass(); 124 } 125 throw new IllegalArgumentException("Didn't find field '" + param.getName() + "' in " + rootInstance); 126 } 127 128 private Object findCollectionField(final Object rootInstance, final ParameterMeta param) { 129 final Collection<?> collection = Collection.class.cast(rootInstance); 130 if (!collection.isEmpty()) { 131 final Object next = collection.iterator().next(); 132 if (param.getPath().endsWith("[${index}]")) { 133 return next; 134 } 135 return findField(next, param); 136 } 137 return null; 138 } 139 140 private String findName(final ParameterMeta meta) { 141 return ofNullable(meta.getSource()).map(ParameterMeta.Source::name).orElse(meta.getName()); 142 } 143 144 private Object tryCreatingObjectInstance(final Type javaType) { 145 final Class<?> type = Class.class.cast(javaType); 146 if (type.isPrimitive()) { 147 if (int.class == type) { 148 return 0; 149 } 150 if (long.class == type) { 151 return 0L; 152 } 153 if (double.class == type) { 154 return 0.; 155 } 156 if (float.class == type) { 157 return 0f; 158 } 159 if (short.class == type) { 160 return (short) 0; 161 } 162 if (byte.class == type) { 163 return (byte) 0; 164 } 165 if (boolean.class == type) { 166 return false; 167 } 168 throw new IllegalArgumentException("Not a primitive: " + type); 169 } 170 if (type.getName().startsWith("java.") || type.getName().startsWith("javax.")) { 171 return null; 172 } 173 try { 174 return type.getConstructor().newInstance(); 175 } catch (final NoSuchMethodException | InstantiationException | IllegalAccessException 176 | InvocationTargetException e) { 177 // ignore, we'll skip the defaults 178 } 179 return null; 180 } 181 182 @Data 183 @AllArgsConstructor(access = PRIVATE) 184 public static class Instance { 185 186 private final Object value; 187 188 private final boolean created; 189 } 190}