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.xbean.registry;
017
018import static java.util.Optional.ofNullable;
019import static org.talend.sdk.component.runtime.manager.util.Lazy.lazy;
020
021import java.lang.reflect.Type;
022import java.util.Date;
023import java.util.Map;
024import java.util.Objects;
025import java.util.Optional;
026import java.util.ServiceLoader;
027import java.util.function.BiFunction;
028import java.util.function.Function;
029import java.util.function.Supplier;
030import java.util.stream.Stream;
031
032import org.apache.xbean.propertyeditor.AbstractConverter;
033import org.apache.xbean.propertyeditor.BigDecimalEditor;
034import org.apache.xbean.propertyeditor.BigIntegerEditor;
035import org.apache.xbean.propertyeditor.BooleanEditor;
036import org.apache.xbean.propertyeditor.CharacterEditor;
037import org.apache.xbean.propertyeditor.ClassEditor;
038import org.apache.xbean.propertyeditor.Converter;
039import org.apache.xbean.propertyeditor.DateEditor;
040import org.apache.xbean.propertyeditor.DoubleEditor;
041import org.apache.xbean.propertyeditor.FileEditor;
042import org.apache.xbean.propertyeditor.HashMapEditor;
043import org.apache.xbean.propertyeditor.HashtableEditor;
044import org.apache.xbean.propertyeditor.Inet4AddressEditor;
045import org.apache.xbean.propertyeditor.Inet6AddressEditor;
046import org.apache.xbean.propertyeditor.InetAddressEditor;
047import org.apache.xbean.propertyeditor.ListEditor;
048import org.apache.xbean.propertyeditor.MapEditor;
049import org.apache.xbean.propertyeditor.ObjectNameEditor;
050import org.apache.xbean.propertyeditor.PatternConverter;
051import org.apache.xbean.propertyeditor.PropertiesEditor;
052import org.apache.xbean.propertyeditor.PropertyEditorRegistry;
053import org.apache.xbean.propertyeditor.SetEditor;
054import org.apache.xbean.propertyeditor.SortedMapEditor;
055import org.apache.xbean.propertyeditor.SortedSetEditor;
056import org.apache.xbean.propertyeditor.StringEditor;
057import org.apache.xbean.propertyeditor.URIEditor;
058import org.apache.xbean.propertyeditor.URLEditor;
059import org.talend.sdk.component.runtime.manager.xbean.converter.LocalDateConverter;
060import org.talend.sdk.component.runtime.manager.xbean.converter.LocalDateTimeConverter;
061import org.talend.sdk.component.runtime.manager.xbean.converter.LocalTimeConverter;
062import org.talend.sdk.component.runtime.manager.xbean.converter.SchemaConverter;
063import org.talend.sdk.component.runtime.manager.xbean.converter.ZonedDateTimeConverter;
064
065public class EnrichedPropertyEditorRegistry extends PropertyEditorRegistry {
066
067    private final ThreadLocal<Map<Type, Optional<Converter>>> converterCache = new ThreadLocal<>();
068
069    public EnrichedPropertyEditorRegistry() {
070        final DoubleEditor doubleEditor = new DoubleEditor();
071        // the front always sends us doubles so
072        // if we don't map double to the native number we get number format exceptions
073        final BiFunction<Class<?>, Function<Double, Object>, Converter> numberConverter =
074                (type, mapper) -> new AbstractConverter(type) {
075
076                    @Override
077                    protected Object toObjectImpl(final String text) {
078                        if (text.isEmpty()) {
079                            return null;
080                        }
081                        final Object o = doubleEditor.toObject(text);
082                        return mapper.apply(Double.class.cast(o));
083                    }
084                };
085
086        // built-in (was provided by xbean originally)
087        super.register(new BooleanEditor());
088        super.register(numberConverter.apply(Byte.class, Double::byteValue));
089        super.register(numberConverter.apply(Short.class, Double::shortValue));
090        super.register(numberConverter.apply(Integer.class, Double::intValue));
091        super.register(numberConverter.apply(Long.class, Double::longValue));
092        super.register(numberConverter.apply(Float.class, Double::floatValue));
093        super.register(doubleEditor);
094        super.register(new BigDecimalEditor());
095        super.register(new BigIntegerEditor());
096        super.register(new StringEditor());
097        super.register(new CharacterEditor());
098        super.register(new ClassEditor());
099        super.register(new LazyDateEditor());
100        super.register(new FileEditor());
101        super.register(new HashMapEditor());
102        super.register(new HashtableEditor());
103        super.register(new Inet4AddressEditor());
104        super.register(new Inet6AddressEditor());
105        super.register(new InetAddressEditor());
106        super.register(new ListEditor());
107        super.register(new SetEditor());
108        super.register(new MapEditor());
109        super.register(new SortedMapEditor());
110        super.register(new SortedSetEditor());
111        super.register(new ObjectNameEditor());
112        super.register(new PropertiesEditor());
113        super.register(new URIEditor());
114        super.register(new URLEditor());
115        super.register(new PatternConverter());
116
117        // customs
118        super.register(new ZonedDateTimeConverter());
119        super.register(new LocalDateTimeConverter());
120        super.register(new LocalDateConverter());
121        super.register(new LocalTimeConverter());
122
123        // schema and record
124        super.register(new SchemaConverter());
125        // extensions
126        ServiceLoader.load(Converter.class).forEach(this::register);
127    }
128
129    @Override
130    public Converter findConverter(final Type type) {
131        final Map<Type, Optional<Converter>> cache = converterCache.get();
132        if (cache == null) {
133            converterCache.remove();
134            return doFindConverter(type);
135        }
136        return ofNullable(cache.get(type)).flatMap(c -> c).orElseGet(() -> {
137            final Converter converter = doFindConverter(type);
138            cache.put(type, ofNullable(converter));
139            return converter;
140        });
141    }
142
143    public <T> T withCache(final Map<Type, Optional<Converter>> cache, final Supplier<T> task) {
144        converterCache.set(cache);
145        try {
146            return task.get();
147        } finally {
148            converterCache.remove();
149        }
150    }
151
152    private Converter doFindConverter(final Type type) {
153        return Stream
154                .<Supplier<Converter>> of(() -> findInternalConverter(type), () -> findStructuralConverter(type))
155                .map(Supplier::get)
156                .filter(Objects::nonNull)
157                .findFirst()
158                .orElse(null);
159    }
160
161    @Override
162    public Converter register(final Converter converter) {
163        final Map<Type, Optional<Converter>> cache = converterCache.get();
164        if (cache != null) {
165            cache.putIfAbsent(converter.getType(), ofNullable(converter));
166        } else {
167            converterCache.remove();
168        }
169        return converter; // avoid unexpected caching (would leak)
170    }
171
172    // used when default init is slow
173    private static class LazyEditor<T extends AbstractConverter> extends AbstractConverter {
174
175        private final Supplier<T> delegate;
176
177        private LazyEditor(final Supplier<T> delegate, final Class<?> type) {
178            super(type);
179            this.delegate = delegate;
180        }
181
182        @Override
183        protected String toStringImpl(final Object value) {
184            return delegate.get().toString(value);
185        }
186
187        @Override
188        protected Object toObjectImpl(final String text) {
189            return delegate.get().toObject(text);
190        }
191    }
192
193    private static class LazyDateEditor extends LazyEditor<DateEditor> {
194
195        private LazyDateEditor() {
196            super(lazy(DateEditor::new), Date.class);
197        }
198    }
199
200}