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  */
  24 package com.sun.hotspot.igv.data;
  25 
  26 import java.io.Serializable;
  27 import java.lang.ref.WeakReference;
  28 import java.util.*;
  29 import java.util.Map.Entry;
  30 import java.util.regex.Matcher;
  31 import java.util.regex.Pattern;
  32 import java.util.regex.PatternSyntaxException;
  33 
  34 /**
  35  *
  36  * @author Thomas Wuerthinger
  37  */
  38 public 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 }