aQute.libg.parameters.Attributes Maven / Gradle / Ivy
The newest version!
package aQute.libg.parameters;
import static java.lang.invoke.MethodHandles.publicLookup;
import static java.lang.invoke.MethodType.methodType;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import aQute.libg.qtokens.QuotedTokenizer;
public class Attributes implements Map {
private static final String ORG_OSGI_FRAMEWORK_VERSION = "org.osgi.framework.Version";
private static final MethodHandle newVersion;
static {
MethodHandle mh;
try {
Class> versionType = Attributes.class.getClassLoader()
.loadClass(ORG_OSGI_FRAMEWORK_VERSION);
try {
mh = publicLookup().findStatic(versionType, "parseVersion", methodType(versionType, String.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
mh = publicLookup().findConstructor(versionType, methodType(void.class, String.class));
}
} catch (Exception e) {
mh = null;
}
newVersion = mh;
}
public enum Type {
STRING(null, "String"),
LONG(null, "Long"),
VERSION(null, "Version"),
DOUBLE(null, "Double"),
STRINGS(STRING, "List"),
LONGS(LONG, "List"),
VERSIONS(VERSION, "List"),
DOUBLES(DOUBLE, "List");
final Type sub;
final String toString;
Type(Type sub, String toString) {
this.sub = sub;
this.toString = toString;
}
@Override
public String toString() {
return toString;
}
public Type plural() {
return switch (this) {
case DOUBLE -> DOUBLES;
case LONG -> LONGS;
case STRING -> STRINGS;
case VERSION -> VERSIONS;
default -> null;
};
}
}
public interface DataType {
Type type();
}
public static final DataType STRING = () -> Type.STRING;
public static final DataType LONG = () -> Type.LONG;
public static final DataType DOUBLE = () -> Type.DOUBLE;
public static final DataType VERSION = () -> Type.VERSION;
public static final DataType> LIST_STRING = () -> Type.STRINGS;
public static final DataType> LIST_LONG = () -> Type.LONGS;
public static final DataType> LIST_DOUBLE = () -> Type.DOUBLES;
public static final DataType> LIST_VERSION = () -> Type.VERSIONS;
public static final Attributes EMPTY_ATTRS = new Attributes(Collections.emptyMap(),
Collections.emptyMap(), ';');
/**
* Pattern for List with list type
*/
static final Pattern TYPED = Pattern
.compile("List\\s*<\\s*(String|Version|Long|Double)\\s*>");
final Map map;
final Map types;
final char separator;
Attributes(Map map, Map types, char separator) {
this.map = map;
this.types = types;
this.separator = separator;
}
public Attributes() {
this(new LinkedHashMap<>(), new HashMap<>(), ';');
}
public Attributes(Attributes... attrs) {
this();
for (Attributes a : attrs) {
if (a != null) {
putAll(a);
}
}
}
// do not remove, used to make sur Attrs use this one and not the next that
// takes a Map
public Attributes(Attributes attrs) {
this();
putAll(attrs);
}
public Attributes(Map v) {
this();
putAll(v);
}
/**
*
* attributes ::= keyvalye ( ',' keyvalue )*
* keyvalue ::= key '=' value
*
*
* @param s attributes or null or empty
*/
public Attributes(String s) {
this(new LinkedHashMap<>(), new HashMap<>(), ',');
if (s != null && !s.isEmpty() && !s.isBlank()) {
QuotedTokenizer qt = new QuotedTokenizer(s, "");
do {
String keyOrAttribute = qt.nextToken("=,");
ParameterMap.error(qt, keyOrAttribute == null, "expected a clause key or attribute key");
switch (qt.getSeparator()) {
case '=' :
String attributeValue = qt.nextToken(",");
ParameterMap.error(qt, attributeValue == null, "expected an attribute value");
put(keyOrAttribute, attributeValue);
break;
case ',' :
break;
default :
ParameterMap.error(qt, true, "unrecognized separator ");
}
} while (qt.getSeparator() == ',');
}
}
public void putAllTyped(Map attrs) {
for (Map.Entry entry : attrs.entrySet()) {
Object value = entry.getValue();
String key = entry.getKey();
putTyped(key, value);
}
}
public void putTyped(String key, Object value) {
if (value == null) {
put(key, null);
return;
}
if (!(value instanceof String)) {
Type type;
if (value instanceof Collection> collection)
value = collection.toArray();
if (value.getClass()
.isArray()) {
type = Type.STRINGS;
int l = Array.getLength(value);
StringBuilder sb = new StringBuilder();
String del = "";
boolean first = true;
for (int i = 0; i < l; i++) {
Object member = Array.get(value, i);
if (member == null) {
// TODO What do we do with null members?
continue;
} else if (first) {
type = getObjectType(member).plural();
first = true;
}
sb.append(del);
int n = sb.length();
sb.append(member);
while (n < sb.length()) {
char c = sb.charAt(n);
if (c == '\\' || c == ',') {
sb.insert(n, '\\');
n++;
}
n++;
}
del = ",";
}
value = sb;
} else {
type = getObjectType(value);
}
if (!key.endsWith(":")) { // directives are only String type
key += ":" + type.toString();
}
}
put(key, value.toString());
}
private Type getObjectType(Object member) {
if (member instanceof Double || member instanceof Float)
return Type.DOUBLE;
if (member instanceof Number)
return Type.LONG;
if (member.getClass()
.getName()
.equals(ORG_OSGI_FRAMEWORK_VERSION))
return Type.VERSION;
return Type.STRING;
}
@Override
public void clear() {
map.clear();
types.clear();
}
public boolean containsKey(String name) {
return map.containsKey(name);
}
@Override
@SuppressWarnings("cast")
@Deprecated
public boolean containsKey(Object name) {
assert name instanceof String;
return map.containsKey(name);
}
public boolean containsValue(String value) {
return map.containsValue(value);
}
@Override
@SuppressWarnings("cast")
@Deprecated
public boolean containsValue(Object value) {
assert value instanceof String;
return map.containsValue(value);
}
@Override
public Set> entrySet() {
return map.entrySet();
}
@Override
@SuppressWarnings("cast")
@Deprecated
public String get(Object key) {
assert key instanceof String;
return map.get(key);
}
public String get(String key) {
return map.get(key);
}
public String get(String key, String deflt) {
String s = get(key);
if (s == null)
return deflt;
return s;
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public Set keySet() {
return map.keySet();
}
@Override
public String put(String key, String value) {
if (key == null)
return null;
int colon = key.indexOf(':');
if (colon >= 0) {
String type = key.substring(colon + 1)
.trim();
typed_attribute: if (!type.isEmpty()) { // typed attribute
String attribute = key.substring(0, colon)
.trim();
switch (type) {
case "String" :
types.remove(attribute);
break;
case "Long" :
types.put(attribute, Type.LONG);
break;
case "Double" :
types.put(attribute, Type.DOUBLE);
break;
case "Version" :
types.put(attribute, Type.VERSION);
break;
case "List" :
case "List" :
types.put(attribute, Type.STRINGS);
break;
case "List" :
types.put(attribute, Type.LONGS);
break;
case "List" :
types.put(attribute, Type.DOUBLES);
break;
case "List" :
types.put(attribute, Type.VERSIONS);
break;
default :
Matcher m = TYPED.matcher(type);
if (!m.matches()) {
break typed_attribute;
}
switch (m.group(1)) {
case "String" :
types.put(attribute, Type.STRINGS);
break;
case "Long" :
types.put(attribute, Type.LONGS);
break;
case "Double" :
types.put(attribute, Type.DOUBLES);
break;
case "Version" :
types.put(attribute, Type.VERSIONS);
break;
}
break;
}
return map.put(attribute, value);
}
}
// default String type
types.remove(key);
return map.put(key, value);
}
public Type getType(String key) {
Type t = types.get(key);
if (t == null)
return Type.STRING;
return t;
}
public void putAll(Attributes attrs) {
types.keySet()
.removeAll(attrs.map.keySet());
map.putAll(attrs.map);
types.putAll(attrs.types);
}
@Override
public void putAll(Map extends String, ? extends String> other) {
if (other instanceof Attributes a) {
putAll(a);
return;
}
for (Map.Entry extends String, ? extends String> e : other.entrySet()) {
put(e.getKey(), e.getValue());
}
}
@Override
@SuppressWarnings("cast")
@Deprecated
public String remove(Object var0) {
assert var0 instanceof String;
types.remove(var0);
return map.remove(var0);
}
public String remove(String var0) {
types.remove(var0);
return map.remove(var0);
}
@Override
public int size() {
return map.size();
}
@Override
public Collection values() {
return map.values();
}
public String getVersion() {
return get("version");
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
append(sb);
return sb.toString();
}
public void append(StringBuilder appendable) {
boolean first = true;
for (Map.Entry e : entrySet()) {
if (!first || separator == ';') {
appendable.append(separator);
}
first = false;
append(appendable, e);
}
}
public void append(StringBuilder sb, Map.Entry e) {
append(sb, e.getKey(), e.getValue());
}
public String toString(String key) {
StringBuilder sb = new StringBuilder();
append(sb, key, get(key));
return sb.toString();
}
private void append(StringBuilder sb, String key, String value) {
sb.append(key);
Type type = getType(key);
if (type != Type.STRING) {
sb.append(":")
.append(type);
}
sb.append("=");
QuotedTokenizer.quote(sb, value);
}
@Override
@Deprecated
public boolean equals(Object other) {
return super.equals(other);
}
@Override
@Deprecated
public int hashCode() {
return super.hashCode();
}
public boolean isEqual(Attributes other) {
if (this == other)
return true;
if (other == null || size() != other.size())
return false;
if (isEmpty())
return true;
TreeSet l = new TreeSet<>(keySet());
TreeSet lo = new TreeSet<>(other.keySet());
if (!l.equals(lo))
return false;
for (String key : keySet()) {
if (!Objects.equals(get(key), other.get(key))) {
return false;
}
if (getType(key) != other.getType(key)) {
return false;
}
}
return true;
}
public Object getTyped(String adname) {
String s = get(adname);
if (s == null)
return null;
Type t = getType(adname);
return convert(t, s);
}
@SuppressWarnings("unchecked")
public T getTyped(DataType type, String adname) {
String s = get(adname);
if (s == null)
return null;
Type t = getType(adname);
if (t != type.type())
throw new IllegalArgumentException(
"For key " + adname + ", expected " + type.type() + " but had a " + t + ". Value is " + s);
return (T) convert(t, s);
}
public static Type toType(String type) {
if (type == null) {
return null;
}
for (Type t : Type.values()) {
if (t.toString.equals(type))
return t;
}
return null;
}
public static Object convert(String t, String s) {
if (s == null)
return null;
Type type = toType(t);
if (type == null)
return s;
return convert(type, s);
}
public static Object convert(Type t, String s) {
if (t.sub == null) {
switch (t) {
case STRING :
return s;
case LONG :
return Long.parseLong(s.trim());
case VERSION :
if (newVersion != null)
try {
return newVersion.invoke(s);
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable e) {
// ignore
}
return s;
case DOUBLE :
return Double.parseDouble(s.trim());
case DOUBLES :
case LONGS :
case STRINGS :
case VERSIONS :
// Cannot happen since the sub is null
return null;
}
return null;
}
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy