/*
* RHQ Management Platform
* Copyright (C) 2005-2011 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.core.util;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* This map is basically an extension of the {@link Properties} class that can resolve the references
* to values of other keys inside the values.
* <p>
* I.e., if the map is initialized with the following mappings:
* <p>
* <code>
* name => world <br />
* hello => Hello ${name}!
* </code>
* <p>
* then the call to:
* <p>
* <code>
* get("hello")
* </code>
* <p>
* will return:
* <code>
* "Hello world!"
* </code>
* <p>
* To access and modify the underlying unprocessed values, one can use the "raw" counterparts of the standard
* map methods (e.g. instead of {@link #get(Object)}, use {@link #getRaw(Object)}, etc.).
*
* @author Lukas Krejci
*/
public class TokenReplacingProperties extends HashMap<String, String> {
private static final long serialVersionUID = 1L;
private Map<String, String> wrapped;
private Map<Object, String> resolved = new HashMap<Object, String>();
private class Entry implements Map.Entry<String, String> {
private Map.Entry<String, String> wrapped;
private boolean process;
public Entry(Map.Entry<String, String> wrapped, boolean process) {
this.wrapped = wrapped;
this.process = process;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Entry)) {
return false;
}
Entry other = (Entry) obj;
String key = wrapped.getKey();
String otherKey = other.getKey();
String value = getValue();
String otherValue = other.getValue();
return (key == null ? otherKey == null : key.equals(otherKey)) &&
(value == null ? otherValue == null : value.equals(otherValue));
}
public String getKey() {
return wrapped.getKey();
}
public String getValue() {
if (process) {
return get(wrapped.getKey());
} else {
return wrapped.getValue();
}
}
@Override
public int hashCode() {
String key = wrapped.getKey();
String value = getValue();
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
public String setValue(String value) {
resolved.remove(wrapped.getKey());
return wrapped.setValue(value);
}
@Override
public String toString() {
return wrapped.toString();
}
}
public TokenReplacingProperties(Map<String, String> wrapped) {
this.wrapped = wrapped;
}
@SuppressWarnings("unchecked")
public TokenReplacingProperties(Properties properties) {
@SuppressWarnings("rawtypes")
Map map = properties;
this.wrapped = (Map<String, String>) map;
}
@Override
public String get(Object key) {
if (resolved.containsKey(key)) {
return resolved.get(key);
}
String rawValue = getRaw(key);
if (rawValue == null) {
return null;
}
String ret = readAll(new TokenReplacingReader(new StringReader(rawValue.toString()), this));
resolved.put(key, ret);
return ret;
}
public String getRaw(Object key) {
return wrapped.get(key);
}
@Override
public String put(String key, String value) {
resolved.remove(key);
return wrapped.put(key, value);
}
@Override
public void putAll(Map<? extends String, ? extends String> m) {
for(String key : m.keySet()) {
resolved.remove(key);
}
wrapped.putAll(m);
}
public void putAll(Properties properties) {
for(String propName : properties.stringPropertyNames()) {
put(propName, properties.getProperty(propName));
}
}
@Override
public void clear() {
wrapped.clear();
resolved.clear();
}
@Override
public boolean containsKey(Object key) {
return wrapped.containsKey(key);
}
@Override
public Set<String> keySet() {
return wrapped.keySet();
}
@Override
public boolean containsValue(Object value) {
for(String key : keySet()) {
String thisVal = get(key);
if (thisVal == null) {
if (value == null) {
return true;
}
} else {
if (thisVal.equals(value)) {
return true;
}
}
}
return false;
}
/**
* Checks whether this map contains the unprocessed value.
*
* @param value
* @return
*/
public boolean containsRawValue(Object value) {
return wrapped.containsValue(value);
}
/**
* The returned set <b>IS NOT</b> backed by this map
* (unlike in the default map implementations).
* <p>
* The {@link java.util.Map.Entry#setValue(Object)} method
* does modify this map though.
*/
@Override
public Set<Map.Entry<String, String>> entrySet() {
Set<Map.Entry<String, String>> ret = new HashSet<Map.Entry<String, String>>();
for(Map.Entry<String, String> entry : wrapped.entrySet()) {
ret.add(new Entry(entry, true));
}
return ret;
}
public Set<Map.Entry<String, String>> getRawEntrySet() {
Set<Map.Entry<String, String>> ret = new HashSet<Map.Entry<String, String>>();
for(Map.Entry<String, String> entry : wrapped.entrySet()) {
ret.add(new Entry(entry, false));
}
return ret;
}
@Override
public String remove(Object key) {
resolved.remove(key);
return wrapped.remove(key).toString();
}
@Override
public int size() {
return wrapped.size();
}
/**
* Unlike in the default implementation the collection returned
* from this method <b>IS NOT</b> backed by this map.
*/
@Override
public Collection<String> values() {
List<String> ret = new ArrayList<String>();
for(String key : keySet()) {
ret.add(get(key));
}
return ret;
}
public Collection<String> getRawValues() {
List<String> ret = new ArrayList<String>();
for(String key : keySet()) {
ret.add(wrapped.get(key));
}
return ret;
}
private String readAll(Reader rdr) {
int in = -1;
StringBuilder bld = new StringBuilder();
try {
while ((in = rdr.read()) != -1) {
bld.append((char) in);
}
} catch (IOException e) {
throw new IllegalStateException("Exception while reading a string.", e);
}
return bld.toString();
}
}