/*********************************************************************************************************************** * Copyright (C) 2010-2013 by the Stratosphere project (http://stratosphere.eu) * * 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. **********************************************************************************************************************/ package eu.stratosphere.configuration; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.apache.commons.codec.binary.Base64; import eu.stratosphere.core.io.IOReadableWritable; import eu.stratosphere.core.io.StringRecord; /** * Lightweight configuration object which can store key/value pairs. Configuration objects * can be extracted from or integrated into the {@link GlobalConfiguration} object. They can * be transported via Nephele's IPC system to distribute configuration data at runtime. * This class is thread-safe. * */ public class Configuration implements IOReadableWritable { /** * Stores the concrete key/value pairs of this configuration object. */ private Map<String, String> confData = new HashMap<String, String>(); /** * The class loader to be used for the <code>getClass</code> method. */ private ClassLoader classLoader; /** * Constructs a new configuration object. */ public Configuration() { this.classLoader = this.getClass().getClassLoader(); } /** * Constructs a new configuration object. * * @param classLoader * the class loader to be use for the <code>getClass</code> method */ public Configuration(final ClassLoader classLoader) { this.classLoader = classLoader; } /** * @return the class loader that knows where to locate user classes */ public ClassLoader getClassLoader() { return this.classLoader; } /** * Sets the class loader that knows where to locate user classes * * @param classLoader * the class loader to be use for the <code>getClass</code> method */ public void setClassLoader(ClassLoader classLoader) { this.classLoader = classLoader; } // -------------------------------------------------------------------------------------------- /** * Returns the class associated with the given key as a string. * * @param <T> * the ancestor of both the default value and the potential value * @param key * the key pointing to the associated value * @param defaultValue * the optional default value returned if no entry exists * @param ancestor * the ancestor of both the default value and the potential value * @return the (default) value associated with the given key * @throws IllegalStateException * if the class identified by the associated value cannot be resolved * @see #setClass(String, Class) */ @SuppressWarnings("unchecked") public <T> Class<T> getClass(String key, Class<? extends T> defaultValue, Class<? super T> ancestor) { String className = getStringInternal(key); if (className == null) { return (Class<T>) defaultValue; } try { return (Class<T>) Class.forName(className, true, this.classLoader); } catch (ClassNotFoundException e) { throw new IllegalStateException(e); } } /** * Returns the class associated with the given key as a string. * * @param key * the key pointing to the associated value * @param defaultValue * the default value which is returned in case there is no value associated with the given key * @return the (default) value associated with the given key * @throws IllegalStateException * if the class identified by the associated value cannot be resolved * @see #setClass(String, Class) */ public Class<?> getClass(String key, Class<?> defaultValue) { return getClass(key, defaultValue, Object.class); } /** * Adds the given key/value pair to the configuration object. The class can be retrieved by invoking * {@link #getClass(String, Class, Class)} if it is in the scope of the class loader on the caller. * * @param key * the key of the pair to be added * @param klazz * the value of the pair to be added * @see #getClass(String, Class) * @see #getClass(String, Class, Class) */ public void setClass(String key, Class<?> klazz) { setStringInternal(key, klazz.getName()); } /** * Returns the value associated with the given key as a string. * * @param key * the key pointing to the associated value * @param defaultValue * the default value which is returned in case there is no value associated with the given key * @return the (default) value associated with the given key */ public String getString(String key, String defaultValue) { String val = getStringInternal(key); return val == null ? defaultValue : val; } /** * Adds the given key/value pair to the configuration object. * * @param key * the key of the key/value pair to be added * @param value * the value of the key/value pair to be added */ public void setString(String key, String value) { setStringInternal(key, value); } /** * Returns the value associated with the given key as an integer. * * @param key * the key pointing to the associated value * @param defaultValue * the default value which is returned in case there is no value associated with the given key * @return the (default) value associated with the given key */ public int getInteger(String key, int defaultValue) { String val = getStringInternal(key); if (val == null) { return defaultValue; } else { return Integer.parseInt(val); } } /** * Adds the given key/value pair to the configuration object. * * @param key * the key of the key/value pair to be added * @param value * the value of the key/value pair to be added */ public void setInteger(String key, int value) { setStringInternal(key, Integer.toString(value)); } /** * Returns the value associated with the given key as a long. * * @param key * the key pointing to the associated value * @param defaultValue * the default value which is returned in case there is no value associated with the given key * @return the (default) value associated with the given key */ public long getLong(String key, long defaultValue) { String val = getStringInternal(key); if (val == null) { return defaultValue; } else { return Long.parseLong(val); } } /** * Adds the given key/value pair to the configuration object. * * @param key * the key of the key/value pair to be added * @param value * the value of the key/value pair to be added */ public void setLong(String key, long value) { setStringInternal(key, Long.toString(value)); } /** * Returns the value associated with the given key as a boolean. * * @param key * the key pointing to the associated value * @param defaultValue * the default value which is returned in case there is no value associated with the given key * @return the (default) value associated with the given key */ public boolean getBoolean(String key, boolean defaultValue) { String val = getStringInternal(key); if (val == null) { return defaultValue; } else { return Boolean.parseBoolean(val); } } /** * Adds the given key/value pair to the configuration object. * * @param key * the key of the key/value pair to be added * @param value * the value of the key/value pair to be added */ public void setBoolean(final String key, final boolean value) { setStringInternal(key, Boolean.toString(value)); } /** * Returns the value associated with the given key as a float. * * @param key * the key pointing to the associated value * @param defaultValue * the default value which is returned in case there is no value associated with the given key * @return the (default) value associated with the given key */ public float getFloat(final String key, final float defaultValue) { String val = getStringInternal(key); if (val == null) { return defaultValue; } else { return Float.parseFloat(val); } } /** * Adds the given key/value pair to the configuration object. * * @param key * the key of the key/value pair to be added * @param value * the value of the key/value pair to be added */ public void setFloat(String key, float value) { setStringInternal(key, Float.toString(value)); } /** * Returns the value associated with the given key as a double. * * @param key * the key pointing to the associated value * @param defaultValue * the default value which is returned in case there is no value associated with the given key * @return the (default) value associated with the given key */ public double getDouble(String key, double defaultValue) { String val = getStringInternal(key); if (val == null) { return defaultValue; } else { return Double.parseDouble(val); } } /** * Adds the given key/value pair to the configuration object. * * @param key * the key of the key/value pair to be added * @param value * the value of the key/value pair to be added */ public void setDouble(String key, double value) { setStringInternal(key, Double.toString(value)); } /** * Returns the value associated with the given key as a byte array. * * @param key * The key pointing to the associated value. * @param defaultValue * The default value which is returned in case there is no value associated with the given key. * @return the (default) value associated with the given key. */ public byte[] getBytes(String key, byte[] defaultValue) { String encoded = getStringInternal(key); if (encoded == null) { return defaultValue; } else { return Base64.decodeBase64(encoded.getBytes()); } } /** * Adds the given byte array to the configuration object. If key is <code>null</code> then nothing is added. * * @param key * The key under which the bytes are added. * @param bytes * The bytes to be added. */ public void setBytes(String key, byte[] bytes) { String encoded = new String(Base64.encodeBase64(bytes)); setStringInternal(key, encoded); } /** * Returns the keys of all key/value pairs stored inside this * configuration object. * * @return the keys of all key/value pairs stored inside this configuration object */ public Set<String> keySet() { // Copy key set, so return value is independent from the object's internal data structure final Set<String> retVal = new HashSet<String>(); synchronized (this.confData) { final Iterator<String> it = this.confData.keySet().iterator(); while (it.hasNext()) { retVal.add(it.next()); } } return retVal; } public void addAll(Configuration other) { synchronized (this.confData) { synchronized (other.confData) { this.confData.putAll(other.confData); } } } /** * Adds all entries from the given configuration into this configuration. The keys * are prepended with the given prefix. * * @param other * The configuration whose entries are added to this configuration. * @param prefix * The prefix to prepend. */ public void addAll(Configuration other, String prefix) { final StringBuilder bld = new StringBuilder(); bld.append(prefix); final int pl = bld.length(); synchronized (this.confData) { synchronized (other.confData) { for (Map.Entry<String, String> entry : other.confData.entrySet()) { bld.setLength(pl); bld.append(entry.getKey()); this.confData.put(bld.toString(), entry.getValue()); } } } } // -------------------------------------------------------------------------------------------- private String getStringInternal(String key) { if (key == null) { throw new NullPointerException("Key must not be null."); } synchronized (this.confData) { return this.confData.get(key); } } private void setStringInternal(String key, String value) { if (key == null) { throw new NullPointerException("Key must not be null."); } if (value == null) { throw new NullPointerException("Value must not be null."); } synchronized (this.confData) { this.confData.put(key, value); } } // -------------------------------------------------------------------------------------------- @Override public void read(final DataInput in) throws IOException { synchronized (this.confData) { final int numberOfProperties = in.readInt(); for (int i = 0; i < numberOfProperties; i++) { final String key = StringRecord.readString(in); final String value = StringRecord.readString(in); this.confData.put(key, value); } } } @Override public void write(final DataOutput out) throws IOException { synchronized (this.confData) { out.writeInt(this.confData.size()); final Iterator<String> it = this.confData.keySet().iterator(); while (it.hasNext()) { final String key = it.next(); final String value = this.confData.get(key); StringRecord.writeString(out, key); StringRecord.writeString(out, value); } } } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + confData.hashCode(); return result; } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Configuration other = (Configuration) obj; return confData.equals(other.confData); } @Override public String toString() { return this.confData.toString(); } }