View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.impl;
18  
19  import java.util.Arrays;
20  import java.util.Comparator;
21  import java.util.HashMap;
22  import java.util.Map;
23  import java.util.Objects;
24  
25  import org.apache.logging.log4j.util.BiConsumer;
26  import org.apache.logging.log4j.util.ReadOnlyStringMap;
27  import org.apache.logging.log4j.util.StringMap;
28  import org.apache.logging.log4j.util.TriConsumer;
29  
30  /**
31   * Provides a read-only {@code StringMap} view of a {@code Map<String, String>}.
32   */
33  class JdkMapAdapterStringMap implements StringMap {
34      private static final long serialVersionUID = -7348247784983193612L;
35      private static final String FROZEN = "Frozen collection cannot be modified";
36      private static final Comparator<? super String> NULL_FIRST_COMPARATOR = new Comparator<String>() {
37          @Override
38          public int compare(final String left, final String right) {
39              if (left == null) {
40                  return -1;
41              }
42              if (right == null) {
43                  return 1;
44              }
45              return left.compareTo(right);
46          }
47      };
48  
49      private final Map<String, String> map;
50      private boolean immutable = false;
51      private transient String[] sortedKeys;
52  
53      public JdkMapAdapterStringMap() {
54          this(new HashMap<String, String>());
55      }
56  
57      public JdkMapAdapterStringMap(final Map<String, String> map) {
58          this.map = Objects.requireNonNull(map, "map");
59      }
60  
61      @Override
62      public Map<String, String> toMap() {
63          return map;
64      }
65  
66      private void assertNotFrozen() {
67          if (immutable) {
68              throw new UnsupportedOperationException(FROZEN);
69          }
70      }
71  
72      @Override
73      public boolean containsKey(final String key) {
74          return map.containsKey(key);
75      }
76  
77      @SuppressWarnings("unchecked")
78      @Override
79      public <V> void forEach(final BiConsumer<String, ? super V> action) {
80          final String[] keys = getSortedKeys();
81          for (int i = 0; i < keys.length; i++) {
82              action.accept(keys[i], (V) map.get(keys[i]));
83          }
84      }
85  
86      @SuppressWarnings("unchecked")
87      @Override
88      public <V, S> void forEach(final TriConsumer<String, ? super V, S> action, final S state) {
89          final String[] keys = getSortedKeys();
90          for (int i = 0; i < keys.length; i++) {
91              action.accept(keys[i], (V) map.get(keys[i]), state);
92          }
93      }
94  
95      private String[] getSortedKeys() {
96          if (sortedKeys == null) {
97              sortedKeys = map.keySet().toArray(new String[map.size()]);
98              Arrays.sort(sortedKeys, NULL_FIRST_COMPARATOR);
99          }
100         return sortedKeys;
101     }
102 
103     @SuppressWarnings("unchecked")
104     @Override
105     public <V> V getValue(final String key) {
106         return (V) map.get(key);
107     }
108 
109     @Override
110     public boolean isEmpty() {
111         return map.isEmpty();
112     }
113 
114     @Override
115     public int size() {
116         return map.size();
117     }
118 
119     @Override
120     public void clear() {
121         if (map.isEmpty()) {
122             return;
123         }
124         assertNotFrozen();
125         map.clear();
126         sortedKeys = null;
127     }
128 
129     @Override
130     public void freeze() {
131         immutable = true;
132     }
133 
134     @Override
135     public boolean isFrozen() {
136         return immutable;
137     }
138 
139     @Override
140     public void putAll(final ReadOnlyStringMap source) {
141         assertNotFrozen();
142         source.forEach(PUT_ALL, map);
143         sortedKeys = null;
144     }
145 
146     private static TriConsumer<String, String, Map<String, String>> PUT_ALL = new TriConsumer<String, String, Map<String, String>>() {
147         @Override
148         public void accept(final String key, final String value, final Map<String, String> stringStringMap) {
149             stringStringMap.put(key, value);
150         }
151     };
152 
153     @Override
154     public void putValue(final String key, final Object value) {
155         assertNotFrozen();
156         map.put(key, value == null ? null : String.valueOf(value));
157         sortedKeys = null;
158     }
159 
160     @Override
161     public void remove(final String key) {
162         if (!map.containsKey(key)) {
163             return;
164         }
165         assertNotFrozen();
166         map.remove(key);
167         sortedKeys = null;
168     }
169 
170     @Override
171     public String toString() {
172         final StringBuilder result = new StringBuilder(map.size() * 13);
173         result.append('{');
174         final String[] keys = getSortedKeys();
175         for (int i = 0; i < keys.length; i++) {
176             if (i > 0) {
177                 result.append(", ");
178             }
179             result.append(keys[i]).append('=').append(map.get(keys[i]));
180         }
181         result.append('}');
182         return result.toString();
183     }
184 
185     @Override
186     public boolean equals(final Object object) {
187         if (object == this) {
188             return true;
189         }
190         if (!(object instanceof JdkMapAdapterStringMap)) {
191             return false;
192         }
193         final JdkMapAdapterStringMap other = (JdkMapAdapterStringMap) object;
194         return map.equals(other.map) && immutable == other.immutable;
195     }
196 
197     @Override
198     public int hashCode() {
199         return map.hashCode() + (immutable ? 31 : 0);
200     }
201 }