Using ConfigurationProperties to fill Map in generic way

前端 未结 4 1102
礼貌的吻别
礼貌的吻别 2020-12-13 08:49

I\'m wondering, if there is a generic way to fill a map with properties you just know the prefix.

Assuming there are a bunch of properties like

names         


        
4条回答
  •  孤城傲影
    2020-12-13 09:07

    I wrote myself a MapFilter class to handle this efficiently. Essentially, you create a Map and then filter it by specifying a prefix for the key. There is also a constructor that takes a Properties for convenience.

    Be aware that this just filters the main map. Any changes applied to the filtered map are also applied to the base map, including deletions etc but obviously changes to the main map will not be reflected in the filtered map until something causes a rebuild.

    It is also very easy (and efficient) to filter already filtered maps.

    public class MapFilter implements Map {
    
        // The enclosed map -- could also be a MapFilter.
        final private Map map;
        // Use a TreeMap for predictable iteration order.
        // Store Map.Entry to reflect changes down into the underlying map.
        // The Key is the shortened string. The entry.key is the full string.
        final private Map> entries = new TreeMap<>();
        // The prefix they are looking for in this map.
        final private String prefix;
    
        public MapFilter(Map map, String prefix) {
            // Store my backing map.
            this.map = map;
            // Record my prefix.
            this.prefix = prefix;
            // Build my entries.
            rebuildEntries();
        }
    
        public MapFilter(Map map) {
            this(map, "");
        }
    
        private synchronized void rebuildEntries() {
            // Start empty.
            entries.clear();
            // Build my entry set.
            for (Map.Entry e : map.entrySet()) {
                String key = e.getKey();
                // Retain each one that starts with the specified prefix.
                if (key.startsWith(prefix)) {
                    // Key it on the remainder.
                    String k = key.substring(prefix.length());
                    // Entries k always contains the LAST occurrence if there are multiples.
                    entries.put(k, e);
                }
            }
    
        }
    
        @Override
        public String toString() {
            return "MapFilter (" + prefix + ") of " + map + " containing " + entrySet();
        }
    
        // Constructor from a properties file.
        public MapFilter(Properties p, String prefix) {
            // Properties extends HashTable so it implements Map.
            // I need Map so I wrap it in a HashMap for simplicity.
            // Java-8 breaks if we use diamond inference.
            this(new HashMap((Map) p), prefix);
        }
    
        // Helper to fast filter the map.
        public MapFilter filter(String prefix) {
            // Wrap me in a new filter.
            return new MapFilter<>(this, prefix);
        }
    
        // Count my entries.
        @Override
        public int size() {
            return entries.size();
        }
    
        // Are we empty.
        @Override
        public boolean isEmpty() {
            return entries.isEmpty();
        }
    
        // Is this key in me?
        @Override
        public boolean containsKey(Object key) {
            return entries.containsKey(key);
        }
    
        // Is this value in me.
        @Override
        public boolean containsValue(Object value) {
            // Walk the values.
            for (Map.Entry e : entries.values()) {
                if (value.equals(e.getValue())) {
                    // Its there!
                    return true;
                }
            }
            return false;
        }
    
        // Get the referenced value - if present.
        @Override
        public T get(Object key) {
            return get(key, null);
        }
    
        // Get the referenced value - if present.
        public T get(Object key, T dflt) {
            Map.Entry e = entries.get((String) key);
            return e != null ? e.getValue() : dflt;
        }
    
        // Add to the underlying map.
        @Override
        public T put(String key, T value) {
            T old = null;
            // Do I have an entry for it already?
            Map.Entry entry = entries.get(key);
            // Was it already there?
            if (entry != null) {
                // Yes. Just update it.
                old = entry.setValue(value);
            } else {
                // Add it to the map.
                map.put(prefix + key, value);
                // Rebuild.
                rebuildEntries();
            }
            return old;
        }
    
        // Get rid of that one.
        @Override
        public T remove(Object key) {
            // Do I have an entry for it?
            Map.Entry entry = entries.get((String) key);
            if (entry != null) {
                entries.remove(key);
                // Change the underlying map.
                return map.remove(prefix + key);
            }
            return null;
        }
    
        // Add all of them.
        @Override
        public void putAll(Map m) {
            for (Map.Entry e : m.entrySet()) {
                put(e.getKey(), e.getValue());
            }
        }
    
        // Clear everything out.
        @Override
        public void clear() {
            // Just remove mine.
            // This does not clear the underlying map - perhaps it should remove the filtered entries.
            for (String key : entries.keySet()) {
                map.remove(prefix + key);
            }
            entries.clear();
        }
    
        @Override
        public Set keySet() {
            return entries.keySet();
        }
    
        @Override
        public Collection values() {
            // Roll them all out into a new ArrayList.
            List values = new ArrayList<>();
            for (Map.Entry v : entries.values()) {
                values.add(v.getValue());
            }
            return values;
        }
    
        @Override
        public Set> entrySet() {
            // Roll them all out into a new TreeSet.
            Set> entrySet = new TreeSet<>();
            for (Map.Entry> v : entries.entrySet()) {
                entrySet.add(new Entry<>(v));
            }
            return entrySet;
        }
    
        /**
         * An entry.
         *
         * @param 
         *
         * The type of the value.
         */
        private static class Entry implements Map.Entry, Comparable> {
    
            // Note that entry in the entry is an entry in the underlying map.
            private final Map.Entry> entry;
    
            Entry(Map.Entry> entry) {
                this.entry = entry;
            }
    
            @Override
            public String getKey() {
                return entry.getKey();
            }
    
            @Override
            public T getValue() {
                // Remember that the value is the entry in the underlying map.
                return entry.getValue().getValue();
            }
    
            @Override
            public T setValue(T newValue) {
                // Remember that the value is the entry in the underlying map.
                return entry.getValue().setValue(newValue);
            }
    
            @Override
            public boolean equals(Object o) {
                if (!(o instanceof Entry)) {
                    return false;
                }
                Entry e = (Entry) o;
                return getKey().equals(e.getKey()) && getValue().equals(e.getValue());
            }
    
            @Override
            public int hashCode() {
                return getKey().hashCode() ^ getValue().hashCode();
            }
    
            @Override
            public String toString() {
                return getKey() + "=" + getValue();
            }
    
            @Override
            public int compareTo(Entry o) {
                return getKey().compareTo(o.getKey());
            }
        }
    
        // Simple tests.
        public static void main(String[] args) {
            String[] samples = {
                "Some.For.Me",
                "Some.For.You",
                "Some.More",
                "Yet.More"};
            Map map = new HashMap();
            for (String s : samples) {
                map.put(s, s);
            }
            Map all = new MapFilter(map);
            Map some = new MapFilter(map, "Some.");
            Map someFor = new MapFilter(some, "For.");
            System.out.println("All: " + all);
            System.out.println("Some: " + some);
            System.out.println("Some.For: " + someFor);
        }
    }
    

提交回复
热议问题