001/*
002 * Copyright (c) 2016-2017 Chris K Wensel <chris@wensel.net>. All Rights Reserved.
003 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved.
004 *
005 * Project and contact information: http://www.cascading.org/
006 *
007 * This file is part of the Cascading project.
008 *
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *     http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 */
021
022package cascading.property;
023
024import java.io.Serializable;
025import java.util.Collection;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.Map;
030import java.util.Properties;
031import java.util.Set;
032
033/**
034 * The ConfigDef class allows for the creation of a configuration properties template to be applied to an existing
035 * properties configuration set.
036 * <p>
037 * There are three property modes, {@link Mode#DEFAULT}, {@link Mode#REPLACE}, and {@link Mode#UPDATE}.
038 * <ul>
039 * <li>A DEFAULT property is only applied if there is no existing value in the property set.</li>
040 * <li>A REPLACE property is always applied overriding any previous values.</li>
041 * <li>An UPDATE property is always applied to an existing property. Usually when the property key represent a list of values.</li>
042 * </ul>
043 */
044public class ConfigDef implements Serializable
045  {
046  public enum Mode
047    {
048      DEFAULT, REPLACE, UPDATE
049    }
050
051  public interface Setter
052    {
053    String set( String key, String value );
054
055    String update( String key, String value );
056
057    String get( String key );
058    }
059
060  public interface Getter
061    {
062    String update( String key, String value );
063
064    String get( String key );
065    }
066
067  protected Map<Mode, Map<String, String>> config;
068
069  public ConfigDef()
070    {
071    }
072
073  /**
074   * Method setProperty sets the value to the given key using the {@link Mode#REPLACE} mode.
075   *
076   * @param key   the key
077   * @param value the value
078   * @return the current ConfigDef instance
079   */
080  public ConfigDef setProperty( String key, String value )
081    {
082    return setProperty( Mode.REPLACE, key, value );
083    }
084
085  /**
086   * Method setProperty sets the value to the given key using the given {@link Mode} value.
087   *
088   * @param key   the key
089   * @param value the value
090   * @return the current ConfigDef instance
091   */
092  public ConfigDef setProperty( Mode mode, String key, String value )
093    {
094    getMode( mode ).put( key, value );
095
096    return this;
097    }
098
099  /**
100   * Method setAllProperties sets all the given properties using the given {@link Mode}.
101   * <p>
102   * Note all the given properties are merged with values in the left most instances having
103   * more precedence.
104   *
105   * @param mode
106   * @param properties
107   * @return
108   */
109  public ConfigDef setAllProperties( Mode mode, Properties... properties )
110    {
111    Map<String, String> map = getMode( mode );
112
113    Properties merged = PropertyUtil.merge( properties );
114
115    for( String property : merged.stringPropertyNames() )
116      map.put( property, merged.getProperty( property ) );
117
118    return this;
119    }
120
121  protected Map<String, String> getMode( Mode mode )
122    {
123    if( config == null )
124      config = new HashMap<>();
125
126    if( !config.containsKey( mode ) )
127      config.put( mode, new HashMap<>() );
128
129    return config.get( mode );
130    }
131
132  protected Map<String, String> getModeSafe( Mode mode )
133    {
134    if( config == null )
135      return Collections.emptyMap();
136
137    if( !config.containsKey( mode ) )
138      return Collections.emptyMap();
139
140    return config.get( mode );
141    }
142
143  /**
144   * Returns {@code true} if there are no properties.
145   *
146   * @return true if no properties.
147   */
148  public boolean isEmpty()
149    {
150    return config == null || config.isEmpty();
151    }
152
153  public String apply( String key, Getter getter )
154    {
155    String defaultValue = getModeSafe( Mode.DEFAULT ).get( key );
156    String replaceValue = getModeSafe( Mode.REPLACE ).get( key );
157    String updateValue = getModeSafe( Mode.UPDATE ).get( key );
158
159    String currentValue = getter.get( key );
160
161    if( currentValue == null && replaceValue == null && updateValue == null )
162      return defaultValue;
163
164    if( replaceValue != null )
165      return replaceValue;
166
167    if( updateValue == null )
168      return currentValue;
169
170    if( currentValue == null )
171      return updateValue;
172
173    return getter.update( key, updateValue );
174    }
175
176  public void apply( Mode mode, Setter setter )
177    {
178    if( !config.containsKey( mode ) )
179      return;
180
181    for( String key : config.get( mode ).keySet() )
182      {
183      switch( mode )
184        {
185        case DEFAULT:
186          if( setter.get( key ) == null )
187            setter.set( key, config.get( mode ).get( key ) );
188          break;
189        case REPLACE:
190          setter.set( key, config.get( mode ).get( key ) );
191          break;
192        case UPDATE:
193          setter.update( key, config.get( mode ).get( key ) );
194          break;
195        }
196      }
197    }
198
199  public Collection<String> getAllKeys()
200    {
201    Set<String> keys = new HashSet<String>();
202
203    for( Map<String, String> map : config.values() )
204      keys.addAll( map.keySet() );
205
206    return Collections.unmodifiableSet( keys );
207    }
208  }