All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vectorprint.configuration.Settings Maven / Gradle / Ivy

package com.vectorprint.configuration;

/*
 * #%L
 * VectorPrintConfig3.0
 * %%
 * Copyright (C) 2011 - 2013 VectorPrint
 * %%
 * Licensed 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.
 * #L%
 */
import com.vectorprint.VectorPrintRuntimeException;
import com.vectorprint.configuration.annotation.Setting;
import com.vectorprint.configuration.annotation.SettingsAnnotationProcessor;
import com.vectorprint.configuration.annotation.SettingsField;
import com.vectorprint.configuration.binding.AbstractBindingHelperDecorator;
import com.vectorprint.configuration.binding.settings.EnhancedMapBindingFactory;
import com.vectorprint.configuration.binding.settings.SettingsBindingService;
import com.vectorprint.configuration.decoration.AbstractPropertiesDecorator;
import com.vectorprint.configuration.decoration.DecorationAware;
import java.awt.Color;
import java.io.File;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;

/**
 * Enhances Java Map with support for data types, debugging info, working with
 * default values in code. Internally a backing Map is used which is a HashMap
 * by default. You can provide your own backing map in {@link #Settings(java.util.Map)
 * }, this may not be an EnhancedMap implementation. You cannot subclass this
 * class, instead subclass {@link AbstractPropertiesDecorator} and wrap an
 * instance of this class.
 *
 * @see EnhancedMapBindingFactory
 * @see com.vectorprint.configuration.decoration
 * @see SettingsAnnotationProcessor
 * @see SettingsField
 * @see Setting
 *
 * @author Eduard Drenth at VectorPrint.nl
 */
public final class Settings implements EnhancedMap, DecorationAware {

    private static final long serialVersionUID = 1;
    private static final Logger log = Logger.getLogger(Settings.class.getName());
    private final Map backingMap;

    @Override
    public void listProperties(PrintStream ps) {
        ps.println("settings with id " + getId() + ":");
        ps.println();
        backingMap.entrySet().forEach((entry) -> {
            ps.println(entry.getKey() + "=" + (entry.getValue() != null ? Arrays.asList(entry.getValue()) : ""));
        });
        ps.println("");
        ps.println("settings wrapped by " + decorators.toString());
    }
    private String id;
    private final Map help = new HashMap<>(50);
    private final List> decorators
            = new ArrayList<>(3);
    private AbstractPropertiesDecorator outermostDecorator;

    /**
     * Creates a backing map {@link HashMap#HashMap() ) }.
     *
     */
    public Settings() {
        backingMap = new HashMap<>();
    }

    /**
     * Creates a backing map {@link HashMap#HashMap(int, float) }.
     *
     * @param initialCapacity
     * @param loadFactor
     */
    public Settings(int initialCapacity, float loadFactor) {
        backingMap = new HashMap<>(initialCapacity, loadFactor);
    }

    /**
     * Creates a backing map {@link HashMap#HashMap(int) }.
     *
     * @param initialCapacity
     */
    public Settings(int initialCapacity) {
        backingMap = new HashMap<>(initialCapacity);
    }

    /**
     * Uses the provided Map as backing map, throws an IllegalArgumentException
     * if the map is an instance of EnhancedMap.
     *
     * @param map
     */
    public Settings(Map map) {
        Objects.requireNonNull(map);
        if (map instanceof EnhancedMap) {
            throw new IllegalArgumentException("instance of " + EnhancedMap.class.getName() + " not allowed");
        }
        backingMap = new HashMap<>(map);
    }

    private void debug(Object val, String... keys) {
        debug(val, true, keys);
    }

    private void debug(Object val, boolean defaultVal, String... keys) {
        if (log.isLoggable(Level.FINE)) {
            StringBuilder s = new StringBuilder(String.valueOf(val));
            if (val != null && val.getClass().isArray()) {
                s = new StringBuilder("");
                Class compType = val.getClass().getComponentType();
                if (!compType.isPrimitive()) {
                    for (Object o : (Object[]) val) {
                        s.append(String.valueOf(o)).append("; ");
                    }
                } else if (compType.equals(boolean.class)) {
                    for (boolean o : (boolean[]) val) {
                        s.append(String.valueOf(o)).append("; ");
                    }
                } else if (compType.equals(char.class)) {
                    for (char o : (char[]) val) {
                        s.append(String.valueOf(o)).append("; ");
                    }
                } else if (compType.equals(byte.class)) {
                    for (byte o : (byte[]) val) {
                        s.append(String.valueOf(o)).append("; ");
                    }
                } else if (compType.equals(short.class)) {
                    for (short o : (short[]) val) {
                        s.append(String.valueOf(o)).append("; ");
                    }
                } else if (compType.equals(int.class)) {
                    for (int o : (int[]) val) {
                        s.append(String.valueOf(o)).append("; ");
                    }
                } else if (compType.equals(long.class)) {
                    for (long o : (long[]) val) {
                        s.append(String.valueOf(o)).append("; ");
                    }
                } else if (compType.equals(float.class)) {
                    for (float o : (float[]) val) {
                        s.append(String.valueOf(o)).append("; ");
                    }
                } else if (compType.equals(double.class)) {
                    for (double o : (double[]) val) {
                        s.append(String.valueOf(o)).append("; ");
                    }
                }
            }
            log.fine(String.format("looking for property %s in %s, using value %s", Arrays.asList(keys), getId(), s.append((defaultVal) ? " (default)" : "").toString()));
        }
    }

    @Override
    public final String[] get(Object key) {
        unused.remove(key);
        return backingMap.get(key);
    }

    private String getFirst(String key) {
        if (log.isLoggable(Level.FINE)) {
            debug((key != null) ? backingMap.get(key) : null, false, key);
        }
        if (key == null) {
            return null;
        }
        String[] l = get(key);
        if (l != null) {
            if (l.length > 1) {
                throw new VectorPrintRuntimeException(String.format("more then one value (%s) for %s, expected one",
                        getFactory().getBindingHelper().serializeValue(l), key));
            }
            return l.length == 0 ? null : l[0];
        } else {
            return null;
        }

    }

    /**
     * When only one argument is given it is assumed to be a key and not a
     * default value.
     *
     * @param defaultValue
     * @param keys
     * @return
     */
    @Override
    public String getProperty(String defaultValue, String... keys) {
        boolean defaultIsKey = defaultValue != null && (keys == null || keys.length == 0);
        String key = defaultIsKey
                ? getKey(null, defaultValue)
                : getKey(defaultValue, keys);
        return key == null ? defaultValue : getFirst(key);
    }

    @Override
    public URL getURLProperty(URL defaultValue, String... keys) throws MalformedURLException {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), URL.class);
    }

    @Override
    public File getFileProperty(File defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), File.class);
    }

    @Override
    public float getFloatProperty(Float defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), Float.class);
    }

    @Override
    public boolean getBooleanProperty(Boolean defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), Boolean.class);
    }

    /**
     * determine the {@link #determineKey(java.lang.String...) key to be used}, call {@link #handleNoValue(java.lang.String...) } when no key is found
     * and no default is given.
     *
     * @param defaultVal a default value
     * @param keys the keys to look for
     * @return the key to be used or null to use a default value
     */
    private String getKey(Object defaultVal, String... keys) {
        String key = determineKey(keys);
        if (key == null) {
            if (defaultVal == null) {
                handleNoValue(keys);
            } else {
                debug(defaultVal, key);
            }
        } else {
            return key;
        }
        return null;
    }

    /**
     * throws a {@link NoValueException}
     * @param keys
     */
    private void handleNoValue(String... keys) {
        throw new NoValueException(Arrays.asList(keys) + " not found and default is null");
    }

    /**
     * Return the first key from the list of arguments present in the settings.
     * Logs the key that is returned, maintains
     * {@link #getKeysNotPresent() set of keys not present}.
     * 
     * @see #keyNotPresent(java.lang.String) 
     * @see #keyPresent(java.lang.String) 
     *
     * @param keys
     * @return the first key found in settings or null
     */
    private String determineKey(String... keys) {
        if (keys == null || keys.length == 0 || keys[0] == null) {
            throw new VectorPrintRuntimeException("You should provide at least one key");
        }
        if (keys.length == 1) {
            if (containsKey(keys[0])) {
                if (log.isLoggable(Level.FINE)) {
                    log.fine(String.format("Returning key \"%s\" from %s, it was first found in settings", keys[0], Arrays.asList(keys)));
                }
                if (notPresent.contains(keys[0])) {
                    notPresent.remove(keys[0]);
                }
                return keys[0];
            } else {
                if (!notPresent.contains(keys[0])) {
                    notPresent.add(keys[0]);
                }
                return null;
            }
        }
        for (String k : keys) {
            if (containsKey(k)) {
                if (log.isLoggable(Level.FINE)) {
                    log.fine(String.format("Returning key \"%s\" from %s, it was first found in settings", k, Arrays.asList(keys)));
                }
                if (notPresent.contains(keys[0])) {
                    notPresent.remove(keys[0]);
                }
                return k;
            } else if (!notPresent.contains(k)) {
                notPresent.add(k);
            }

        }
        if (log.isLoggable(Level.FINE)) {
            log.fine(String.format("None of %s found in settings", Arrays.asList(keys)));
        }
        return null;
    }

    @Override
    public double getDoubleProperty(Double defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), Double.class);
    }

    @Override
    public int getIntegerProperty(Integer defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), Integer.class);
    }

    @Override
    public short getShortProperty(Short defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), Short.class);
    }

    @Override
    public char getCharProperty(Character defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), Character.class);
    }

    @Override
    public byte getByteProperty(Byte defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), Byte.class);
    }

    @Override
    public long getLongProperty(Long defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), Long.class);
    }

    @Override
    public Color getColorProperty(Color defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), Color.class);
    }

    @Override
    public String[] getStringProperties(String[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        if (key == null) {
            return defaultValue;
        }
        return get(key);
    }

    @Override
    public URL[] getURLProperties(URL[] defaultValue, String... keys) throws MalformedURLException {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseURLValues(get(key));
    }

    @Override
    public File[] getFileProperties(File[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseFileValues(get(key));
    }

    @Override
    public float[] getFloatProperties(float[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseFloatValues(get(key));
    }

    @Override
    public char[] getCharProperties(char[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseCharValues(get(key));

    }

    @Override
    public short[] getShortProperties(short[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseShortValues(get(key));
    }

    @Override
    public byte[] getByteProperties(byte[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseByteValues(get(key));
    }

    @Override
    public double[] getDoubleProperties(double[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseDoubleValues(get(key));
    }

    @Override
    public int[] getIntegerProperties(int[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseIntValues(get(key));
    }

    @Override
    public boolean[] getBooleanProperties(boolean[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseBooleanValues(get(key));
    }

    @Override
    public Color[] getColorProperties(Color[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseColorValues(get(key));
    }

    /**
     * This implementation only includes key and value state, not the rest of
     * the state (i.e. {@link #getUnusedKeys() }, {@link #getHelp()
     * } and {@link #getKeysNotPresent() }). The {@link #getId() id's} of the
     * objects must be both null or the same, otherwise false is returned.
     *
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj == this) {
            return true;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Settings other = (Settings) obj;
        if ((this.id == null) ? (other.id != null) : !this.id.equals(other.id)) {
            return false;
        }
        return Objects.equals(backingMap, other.backingMap);
    }

    @Override
    public PropertyHelp getHelp(String key) {
        if (help.containsKey(key)) {
            return help.get(key);
        } else {
            return new PropertyHelpImpl("no help configured for " + key);
        }
    }

    /**
     * initializes help for properties
     *
     * @param help
     * @see #getHelp(java.lang.String)
     * @see #getHelp()
     */
    @Override
    public void setHelp(Map help) {
        this.help.clear();
        this.help.putAll(help);
    }

    @Override
    public Map getHelp() {
        return help;
    }

    @Override
    public String printHelp() {
        StringBuilder sb = new StringBuilder(1024);
        help.entrySet().forEach((h) -> {
            sb.append(h.getKey()).append(": ").append(h.getValue().getType())
                    .append("; ")
                    .append(h.getValue().getExplanation())
                    .append(System.getProperty("line.separator"));
        });
        return sb.toString();
    }

    @Override
    public long[] getLongProperties(long[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseLongValues(get(key));
    }

    @Override
    public final String[] put(String key, String[] value) {
        if (!backingMap.containsKey(key)) unused.add(key);
        return backingMap.put(key, value);
    }

    @Override
    public final void clear() {
        unused.clear();
        notPresent.clear();
        help.clear();
        decorators.clear();
        backingMap.clear();
    }

    @Override
    public final String[] remove(Object key) {
        unused.remove(key);
        return backingMap.remove(key);
    }

    private void init(Settings vp) {
        vp.help.putAll(help);
        vp.unused.addAll(keySet());
        vp.id = id;
        vp.notPresent.addAll(notPresent);
        vp.decorators.addAll(decorators);
        vp.outermostDecorator = outermostDecorator;
    }

    /**
     * Creates a new identical Settings object. Note that the id of the clone
     * will equal that of the original. The backing Map will be cloned by
     * calling clone when it is Cloneable, otherwise newInstance and putAll are
     * used to clone the backing Map. The
     * {@link #getOutermostDecorator() outermostDecorator} will be copied, not
     * cloned.
     *
     * @return an identical copy of these Settings.
     */
    @Override
    public Settings clone() {
        Settings vp;
        if (backingMap instanceof Cloneable) {
            try {
                Method m = backingMap.getClass().getMethod("clone");
                vp = new Settings((Map) m.invoke(backingMap, null));
            } catch (InvocationTargetException | IllegalArgumentException | IllegalAccessException | NoSuchMethodException | SecurityException ex) {
                throw new VectorPrintRuntimeException(ex);
            }
        } else {
            try {
                vp = new Settings(backingMap.getClass().newInstance());
            } catch (IllegalAccessException | InstantiationException ex) {
                throw new VectorPrintRuntimeException(ex);
            }
            vp.backingMap.putAll(backingMap);
        }
        init(vp);
        return vp;
    }

    /**
     *
     * @return the id of the properties or null
     */
    @Override
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    /**
     * this implementation supports all primitives and their wrappers, Color,
     * Date, URL, Class and arrays of those types. Calls {@link #getGenericProperty(java.lang.String, java.lang.Object, java.lang.Class)
     * } if one of the keys is present in the settings.
     *
     * @param 
     * @param keys
     * @param defaultValue
     * @param clazz
     * @return value of the setting or the default value
     * @throws VectorPrintRuntimeException when no value is found and
     * defaultValue is null
     */
    public  T getGenericProperty(T defaultValue, Class clazz, String... keys) {
        String key = getKey(defaultValue, keys);
        return getGenericProperty(key, defaultValue, clazz);
    }

    /**
     * this implementation supports all primitives and their wrappers, Color,
     * Date, URL, Class and arrays of those types.
     *
     * @param 
     * @param key
     * @param defaultValue
     * @param clazz
     * @return value of the setting or the default value
     * defaultValue is null
     */
    private  T getGenericProperty(String key, T defaultValue, Class clazz) {
        if (key == null) {
            return defaultValue;
        } else if (clazz.isArray()) {
            if (String[].class.equals(clazz)) {
                return (T) get(key);
            }
            return key == null ? defaultValue : getFactory().getBindingHelper().convert(get(key), clazz);
        } else {
            if (String.class.equals(clazz)) {
                return (T) getFirst(key);
            }
            return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), clazz);
        }
    }

    @Override
    public Date getDateProperty(Date defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), Date.class);
    }

    @Override
    public Date[] getDateProperties(Date[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseDateValues(get(key));
    }

    private final Collection unused = new HashSet<>(25);

    @Override
    public Collection getUnusedKeys() {
        for (Iterator it = unused.iterator(); it.hasNext();) {
            if (!containsKey(it.next())) {
                it.remove();
            }
        }
        return Collections.unmodifiableCollection(unused);
    }

    private final Set notPresent = new HashSet<>(25);

    @Override
    public Collection getKeysNotPresent() {
        return Collections.unmodifiableCollection(notPresent);
    }

    @Override
    protected void finalize() throws Throwable {
        log.warning("not used, possibly obsolete settings in: " + this + ": " + getUnusedKeys());
        super.finalize();
    }

    @Override
    public String toString() {
        return "Settings{" + "id=" + id + ", decorators=" + decorators + '}';
    }
    
    

    /**
     *
     * @param defaultValue the value of defaultValue
     * @param keys the value of keys
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    public Class getClassProperty(Class defaultValue, String... keys) throws ClassNotFoundException {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), Class.class);
    }

    @Override
    public Pattern getRegexProperty(Pattern defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : getFactory().getBindingHelper().convert(getFirst(key), Pattern.class);
    }

    @Override
    public Pattern[] getRegexProperties(Pattern[] defaultValue, String... keys) {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseRegexValues(get(key));
    }

    /**
     *
     * @param defaultValue the value of defaultValue
     * @param keys the value of keys
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    public Class[] getClassProperties(Class[] defaultValue, String... keys) throws ClassNotFoundException {
        String key = getKey(defaultValue, keys);
        return key == null ? defaultValue : AbstractBindingHelperDecorator.parseClassValues(get(key));
    }

    @Override
    public List> getDecorators() {
        return decorators;
    }

    @Override
    public void addDecorator(Class clazz) {
        decorators.add(clazz);
    }

    @Override
    public AbstractPropertiesDecorator getOutermostDecorator() {
        return outermostDecorator;
    }

    @Override
    public void setOutermostDecorator(AbstractPropertiesDecorator outermostDecorator) {
        this.outermostDecorator = outermostDecorator;
        log.warning(String.format("NB! Settings wrapped by %s, you should use this instead of %s", outermostDecorator.getClass().getName(),
                getClass().getName()));
    }

    @Override
    public String[] put(String key, String value) {
        return put(key, new String[]{value});
    }

    @Override
    public boolean containsValue(Object value) {
        if (value == null) {
            return backingMap.containsValue(null);
        }
        if (String[].class.equals(value.getClass())) {

            if (entrySet().stream().anyMatch((e) -> (Arrays.equals(e.getValue(), (String[]) value)))) {
                return true;
            }
        }
        return false;
    }

    private EnhancedMapBindingFactory getFactory() {
        return SettingsBindingService.getInstance().getFactory();
    }

    @Override
    public int size() {
        return backingMap.size();
    }

    @Override
    public boolean isEmpty() {
        return backingMap.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return backingMap.containsKey(key);
    }

    @Override
    public void putAll(Map m) {
        m.forEach((k, v) -> {
            if (!backingMap.containsKey(k)) unused.add(k);
        });
        backingMap.putAll(m);
    }

    @Override
    public void put(Map m) {
        m.forEach((k, v) -> {
            put(k, v);
        });
    }

    @Override
    public Set keySet() {
        return backingMap.keySet();
    }

    @Override
    public Collection values() {
        return backingMap.values();
    }

    @Override
    public Set> entrySet() {
        return backingMap.entrySet();
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy