blob: c7156fb9f46b2f7d54b8cfc46faf99814484c9f5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tamaya.spi;
import org.apache.tamaya.Configuration;
import org.apache.tamaya.TypeLiteral;
import java.lang.reflect.AnnotatedElement;
import java.util.*;
/**
* A conversion context containing all the required values for implementing conversion. Use the included #Builder
* for creating new instances of. This class is thread-safe to use. Adding supported formats is synchronized.
* @see PropertyConverter
*/
public class ConversionContext {
public static final ConversionContext EMPTY = new ConversionContext.Builder(TypeLiteral.of(String.class)).build();
private final Configuration configuration;
private final String key;
private final List<PropertyValue> values;
private final TypeLiteral<?> targetType;
private final AnnotatedElement annotatedElement;
private final Set<String> supportedFormats = new LinkedHashSet<>();
/**
* Private constructor used from builder.
* @param builder the builder, not {@code null}.
*/
protected ConversionContext(Builder builder){
this.key = builder.key;
this.annotatedElement = builder.annotatedElement;
this.targetType = builder.targetType;
this.supportedFormats.addAll(builder.supportedFormats);
this.configuration = builder.configuration;
List<PropertyValue> tempValues = new ArrayList<>();
tempValues.addAll(builder.values);
this.values = Collections.unmodifiableList(tempValues);
}
/**
* Get the key accessed. This information is very useful to evaluate additional metadata needed to determine/
* control further aspects of the conversion.
* @return the key. This may be null in case where a default value has to be converted and no unique underlying
* key/value configuration is present.
*/
public String getKey(){
return key;
}
/**
* Get the correspnoding underlying property values matching the given key, in order of significance
* (most significant last).
* @return the underlying values evaluated, never null.
*/
public List<PropertyValue> getValues(){
return this.values;
}
/**
* Get the target type required.
* @return the target type required.
*/
public TypeLiteral<?> getTargetType(){
return targetType;
}
/**
* Get the annotated element, if conversion is performed using injection mechanisms.
* @return the annotated element, or {@code null}.
*/
public AnnotatedElement getAnnotatedElement(){
return annotatedElement;
}
/**
* Get the configuration, which is targeted.
* @return the configuration instance, or {@code null}.
*/
public Configuration getConfiguration(){
return configuration;
}
/**
* Evaluate the metadata for the current target key from the given values. Later values hereby are more significant.
* @return the evaluated meta data mapProperties.
*/
public Map<String, String> getMeta() {
Map<String, String> metaMap = new HashMap<>();
if(configuration!=null){
metaMap.putAll(configuration.getContext().getMetaData(key));
}
for(PropertyValue val:values){
if(key.equals(val.getQualifiedKey())){
metaMap.putAll(val.getMeta());
}
}
return metaMap;
}
/**
* Allows to addPropertyValue information on the supported/tried formats, which can be shown to the user, especially when
* conversion failed. Adding of formats is synchronized, all formats are added in order to the overall createList.
* This means formats should be passed in order of precedence.
* @param converterType the converters, which implements the formats provided.
* @param formatDescriptors the format descriptions in a human readable form, e.g. as regular expressions.
*/
public void addSupportedFormats(@SuppressWarnings("rawtypes") Class<?> converterType, String... formatDescriptors){
synchronized (supportedFormats){
for(String format: formatDescriptors) {
supportedFormats.add(format + " (" + converterType.getSimpleName() + ")");
}
}
}
/**
* Get the supported/tried formats in precedence order. The contents of this method depends on the
* {@link PropertyConverter} instances involved in a conversion.
* @return the supported/tried formats, never {@code null}.
*/
public List<String> getSupportedFormats(){
synchronized (supportedFormats){
return new ArrayList<>(supportedFormats);
}
}
/**
* Creates a builder based on this instance.
*/
public Builder toBuilder(){
Builder builder = new Builder(key, targetType)
.setConfiguration(this.configuration)
.setValues(this.values);
if(annotatedElement!=null) {
builder.setAnnotatedElement(annotatedElement);
}
builder.supportedFormats.addAll(this.supportedFormats);
return builder;
}
@Override
public String toString() {
return "ConversionContext{" +
"configuration=" + configuration +
", key='" + key + '\'' +
", targetType=" + targetType +
", annotatedElement=" + annotatedElement +
", supportedFormats=" + supportedFormats +
'}';
}
/**
* Get the current configuration context.
* @deprecated Use {@link #getConfiguration()} and {@link Configuration#getContext()}.
* @return the current configuration context.
*/
@Deprecated
public ConfigurationContext getConfigurationContext() {
if(configuration!=null) {
return configuration.getContext();
}
return ConfigurationContext.EMPTY;
}
/**
* Builder to create new instances of {@link ConversionContext}.
*/
public static final class Builder{
/** The backing configuration. */
private Configuration configuration;
/** The accessed key, or null. */
private String key;
/** The corresponding property values, as delivered from the corresponding property sources,
* in order of significance (highest last).
*/
private final List<PropertyValue> values = new ArrayList<>();
/** The target type. */
private TypeLiteral<?> targetType;
/** The injection target (only setCurrent with injection used). */
private AnnotatedElement annotatedElement;
/** The ordered setCurrent of formats tried. */
private final Set<String> supportedFormats = new LinkedHashSet<>();
/**
* Creates a new Builder instance.
* @param targetType the target type
*/
public Builder(TypeLiteral<?> targetType) {
this(null, null, targetType);
}
/**
* Creates a new Builder instance.
* @param key the requested key, may be null.
* @param targetType the target type
*/
public Builder(String key, TypeLiteral<?> targetType) {
this(null, key, targetType);
}
/**
* Creates a new Builder instance.
* @param configuration the configuration, not {@code null}.
* @param key the requested key, may be {@code null}.
* @param targetType the target type
*/
public Builder(Configuration configuration, String key, TypeLiteral<?> targetType){
this.key = key;
this.configuration = configuration;
this.targetType = Objects.requireNonNull(targetType);
}
/**
* Sets the key.
* @param key the key, not {@code null}.
* @return the builder instance, for chaining
*/
public Builder setKey(String key){
this.key = Objects.requireNonNull(key);
return this;
}
/**
* Sets the underlying values evaluated.
* @param values the values, not {@code null}.
* @return the builder instance, for chaining
*/
public Builder setValues(List<PropertyValue> values){
this.values.addAll(values);
return this;
}
/**
* Sets the underlying values evaluated.
* @param values the values, not {@code null}.
* @return the builder instance, for chaining
*/
public Builder setValues(PropertyValue... values){
this.values.addAll(Arrays.asList(values));
return this;
}
/**
* Sets the configuration.
* @param configuration the configuration, not {@code null}
* @return the builder instance, for chaining
*/
public Builder setConfiguration(Configuration configuration){
this.configuration = Objects.requireNonNull(configuration);
return this;
}
/**
* Sets the annotated element, when configuration is injected.
* @param annotatedElement the annotated element, not {@code null}
* @return the builder instance, for chaining
*/
public Builder setAnnotatedElement(AnnotatedElement annotatedElement){
this.annotatedElement = Objects.requireNonNull(annotatedElement);
return this;
}
/**
* Sets the target type explicitly. This is required in some rare cases, e.g. injection of {@code Provider}
* instances, where the provider's result type must be produced.
* @param targetType the
* @return the builder for chaining.
*/
public Builder setTargetType(@SuppressWarnings("rawtypes") TypeLiteral targetType) {
this.targetType = Objects.requireNonNull(targetType);
return this;
}
/**
* Add the formats provided by a {@link PropertyConverter}. This method should be called by each converters
* performing/trying conversion, so the user can be given feedback on the supported formats on failure.
* @param converterType the converters type, not {@code null}.
* @param formatDescriptors the formats supported in a human readable form, e.g. as regular expressions.
* @return the builder instance, for chaining
*/
public Builder addSupportedFormats(@SuppressWarnings("rawtypes") Class<?> converterType, String... formatDescriptors){
for(String format: formatDescriptors) {
supportedFormats.add(format + " (" + converterType.getSimpleName() + ")");
}
return this;
}
/**
* Builds a new context instance.
* @return a new context, never null.
*/
public ConversionContext build(){
return new ConversionContext(this);
}
@Override
public String toString() {
return "Builder{" +
"configuration=" + configuration +
", key='" + key + '\'' +
", targetType=" + targetType +
", annotatedElement=" + annotatedElement +
", supportedFormats=" + supportedFormats +
'}';
}
}
}