1/*
2 * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 *
23 */
24package com.sun.hotspot.igv.data;
25
26import java.io.Serializable;
27import java.lang.ref.WeakReference;
28import java.util.*;
29import java.util.Map.Entry;
30import java.util.regex.Matcher;
31import java.util.regex.Pattern;
32import java.util.regex.PatternSyntaxException;
33
34/**
35 *
36 * @author Thomas Wuerthinger
37 */
38public class Properties implements Serializable, Iterable<Property> {
39
40    public static final long serialVersionUID = 1L;
41    protected String[] map = new String[4];
42
43    public Properties() {
44    }
45
46    @Override
47    public boolean equals(java.lang.Object o) {
48        if (!(o instanceof Properties)) {
49            return false;
50        }
51
52        Properties p = (Properties) o;
53
54        for (Property prop : this) {
55            String value = p.get(prop.getName());
56            if (value == null || !value.equals(prop.getValue())) {
57                return false;
58            }
59        }
60
61        for (Property prop : p) {
62            String value = this.get(prop.getName());
63            if (value == null || !value.equals(prop.getValue())) {
64                return false;
65            }
66        }
67
68        return true;
69    }
70
71    @Override
72    public int hashCode() {
73        int hash = 5;
74
75        if (map != null) {
76            for (int i = 0; i < this.map.length; i++) {
77                if (map[i] == null) {
78                    i++;
79                } else {
80                    hash = hash * 83 + map[i].hashCode();
81                }
82            }
83        }
84        return hash;
85    }
86
87    public Properties(String name, String value) {
88        this();
89        this.setProperty(name, value);
90    }
91
92    public Properties(String name, String value, String name1, String value1) {
93        this(name, value);
94        this.setProperty(name1, value1);
95    }
96
97    public Properties(String name, String value, String name1, String value1, String name2, String value2) {
98        this(name, value, name1, value1);
99        this.setProperty(name2, value2);
100    }
101
102    public Properties(Properties p) {
103        map = new String[p.map.length];
104        System.arraycopy(p.map, 0, map, 0, p.map.length);
105    }
106
107    protected Properties(String[] map) {
108        this.map = map;
109    }
110
111    static class SharedProperties extends Properties {
112        int hashCode;
113
114        SharedProperties(String[] map) {
115            super(map);
116            this.hashCode = Arrays.hashCode(map);
117        }
118
119        @Override
120        protected void setPropertyInternal(String name, String value) {
121            throw new UnsupportedOperationException();
122        }
123
124        @Override
125        public boolean equals(Object other) {
126            if (this == other) {
127                return true;
128            }
129            if (!(other instanceof SharedProperties)) {
130                return super.equals(other);
131            }
132            SharedProperties props2 = (SharedProperties) other;
133            return Arrays.equals(map, props2.map);
134        }
135
136        @Override
137        public int hashCode() {
138            return hashCode;
139        }
140    }
141
142    private static class PropertyCache {
143        static WeakHashMap<SharedProperties, WeakReference<SharedProperties>> immutableCache = new WeakHashMap<>();
144
145        static synchronized SharedProperties intern(Properties properties) {
146            String[] map = properties.map;
147            SharedProperties key = new SharedProperties(map);
148            WeakReference<SharedProperties> entry = immutableCache.get(key);
149            if (entry != null) {
150                SharedProperties props = entry.get();
151                if (props != null) {
152                    return props;
153                }
154            }
155            immutableCache.put(key, new WeakReference<>(key));
156            return key;
157        }
158    }
159
160    public static class Entity implements Provider {
161
162        private Properties properties;
163
164        public Entity() {
165            properties = new Properties();
166        }
167
168        public Entity(Properties.Entity object) {
169            properties = new Properties(object.getProperties());
170        }
171
172        @Override
173        public Properties getProperties() {
174            return properties;
175        }
176
177        public void internProperties() {
178            properties = PropertyCache.intern(properties);
179        }
180    }
181
182    public interface PropertyMatcher {
183
184        String getName();
185
186        boolean match(String value);
187    }
188
189    public static class InvertPropertyMatcher implements PropertyMatcher {
190
191        private PropertyMatcher matcher;
192
193        public InvertPropertyMatcher(PropertyMatcher matcher) {
194            this.matcher = matcher;
195        }
196
197        @Override
198        public String getName() {
199            return matcher.getName();
200        }
201
202        @Override
203        public boolean match(String p) {
204            if (p == null) {
205                return false;
206            }
207            return !matcher.match(p);
208        }
209    }
210
211    public static class StringPropertyMatcher implements PropertyMatcher {
212
213        private String name;
214        private String value;
215
216        public StringPropertyMatcher(String name, String value) {
217            if (name == null) {
218                throw new IllegalArgumentException("Property name must not be null!");
219            }
220            if (value == null) {
221                throw new IllegalArgumentException("Property value must not be null!");
222            }
223            this.name = name;
224            this.value = value;
225        }
226
227        @Override
228        public String getName() {
229            return name;
230        }
231
232        @Override
233        public boolean match(String p) {
234            if (p == null) {
235                throw new IllegalArgumentException("Property value must not be null!");
236            }
237            return p.equals(value);
238        }
239    }
240
241    public static class RegexpPropertyMatcher implements PropertyMatcher {
242
243        private String name;
244        private Pattern valuePattern;
245
246        public RegexpPropertyMatcher(String name, String value) {
247            this(name, value, 0);
248        }
249
250        public RegexpPropertyMatcher(String name, String value, int flags) {
251
252            if (name == null) {
253                throw new IllegalArgumentException("Property name must not be null!");
254            }
255
256            if (value == null) {
257                throw new IllegalArgumentException("Property value pattern must not be null!");
258            }
259
260            this.name = name;
261
262            try {
263                valuePattern = Pattern.compile(value, flags);
264            } catch (PatternSyntaxException e) {
265                throw new IllegalArgumentException("Bad pattern: " + value);
266            }
267        }
268
269        @Override
270        public String getName() {
271            return name;
272        }
273
274        @Override
275        public boolean match(String p) {
276            if (p == null) {
277                throw new IllegalArgumentException("Property value must not be null!");
278            }
279            Matcher m = valuePattern.matcher(p);
280            return m.matches();
281        }
282    }
283
284    public Property selectSingle(PropertyMatcher matcher) {
285
286        final String name = matcher.getName();
287        String value = null;
288        for (int i = 0; i < map.length; i += 2) {
289            if (map[i] != null && name.equals(map[i])) {
290                value = map[i + 1];
291                break;
292            }
293        }
294        if (value != null && matcher.match(value)) {
295            return new Property(name, value);
296        } else {
297            return null;
298        }
299    }
300
301    public interface Provider {
302
303        public Properties getProperties();
304    }
305
306    @Override
307    public String toString() {
308        List<String[]> pairs = new ArrayList<>();
309        for (int i = 0; i < map.length; i += 2) {
310            if (map[i + 1] != null) {
311                pairs.add(new String[]{map[i], map[i + 1]});
312            }
313        }
314
315        Collections.sort(pairs, new Comparator<String[]>() {
316            @Override
317            public int compare(String[] o1, String[] o2) {
318                assert o1.length == 2;
319                assert o2.length == 2;
320                return o1[0].compareTo(o2[0]);
321            }
322        });
323
324        StringBuilder sb = new StringBuilder();
325        sb.append("[");
326        boolean first = true;
327        for (String[] p : pairs) {
328            if (first) {
329                first = false;
330            } else {
331                sb.append(", ");
332            }
333            sb.append(p[0]).append("=").append(p[1]);
334        }
335        return sb.append("]").toString();
336    }
337
338    public static class PropertySelector<T extends Properties.Provider> {
339
340        private Collection<T> objects;
341
342        public PropertySelector(Collection<T> objects) {
343            this.objects = objects;
344        }
345
346        public T selectSingle(PropertyMatcher matcher) {
347
348            for (T t : objects) {
349                Property p = t.getProperties().selectSingle(matcher);
350                if (p != null) {
351                    return t;
352                }
353            }
354
355            return null;
356        }
357
358        public List<T> selectMultiple(PropertyMatcher matcher) {
359            List<T> result = new ArrayList<>();
360
361            for (T t : objects) {
362                Property p = t.getProperties().selectSingle(matcher);
363                if (p != null) {
364                    result.add(t);
365                }
366            }
367
368            return result;
369        }
370    }
371
372    public String get(String key) {
373        for (int i = 0; i < map.length; i += 2) {
374            if (map[i] != null && map[i].equals(key)) {
375                return map[i + 1];
376            }
377        }
378        return null;
379    }
380
381    public void setProperty(String name, String value) {
382        setPropertyInternal(name.intern(), value != null ? value.intern() : null);
383    }
384
385    protected void setPropertyInternal(String name, String value) {
386        for (int i = 0; i < map.length; i += 2) {
387            if (map[i] != null && map[i].equals(name)) {
388                String p = map[i + 1];
389                if (value == null) {
390                    // remove this property
391                    map[i] = null;
392                    map[i + 1] = null;
393                } else {
394                    map[i + 1] = value;
395                }
396                return;
397            }
398        }
399        if (value == null) {
400            return;
401        }
402        for (int i = 0; i < map.length; i += 2) {
403            if (map[i] == null) {
404                map[i] = name;
405                map[i + 1] = value;
406                return;
407            }
408        }
409        String[] newMap = new String[map.length + 4];
410        System.arraycopy(map, 0, newMap, 0, map.length);
411        newMap[map.length] = name;
412        newMap[map.length + 1] = value;
413        map = newMap;
414    }
415
416    public void add(Properties properties) {
417        for (Property p : properties) {
418            // Already interned
419            setPropertyInternal(p.getName(), p.getValue());
420        }
421    }
422
423    private class PropertiesIterator implements Iterator<Property> {
424
425        int index;
426
427        @Override
428        public boolean hasNext() {
429            while (index < map.length && map[index + 1] == null) {
430                index += 2;
431            }
432            return index < map.length;
433        }
434
435        @Override
436        public Property next() {
437            if (index < map.length) {
438                index += 2;
439                return new Property(map[index - 2], map[index - 1]);
440            }
441            return null;
442        }
443
444        @Override
445        public void remove() {
446            throw new UnsupportedOperationException("Not supported yet.");
447        }
448    }
449
450    @Override
451    public Iterator<Property> iterator() {
452        return new PropertiesIterator();
453    }
454}
455