Coverage Report - org.apache.shiro.config.Ini
 
Classes in this File Line Coverage Branch Coverage Complexity
Ini
67%
93/138
63%
37/58
2.22
Ini$1
N/A
N/A
2.22
Ini$Section
75%
69/92
67%
39/58
2.22
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  */
 19  
 package org.apache.shiro.config;
 20  
 
 21  
 import org.apache.shiro.io.ResourceUtils;
 22  
 import org.apache.shiro.util.CollectionUtils;
 23  
 import org.apache.shiro.util.StringUtils;
 24  
 import org.slf4j.Logger;
 25  
 import org.slf4j.LoggerFactory;
 26  
 
 27  
 import java.io.*;
 28  
 import java.util.*;
 29  
 
 30  
 /**
 31  
  * A class representing the <a href="http://en.wikipedia.org/wiki/INI_file">INI</a> text configuration format.
 32  
  * <p/>
 33  
  * An Ini instance is a map of {@link Ini.Section Section}s, keyed by section name.  Each
 34  
  * {@code Section} is itself a map of {@code String} name/value pairs.  Name/value pairs are guaranteed to be unique
 35  
  * within each {@code Section} only - not across the entire {@code Ini} instance.
 36  
  *
 37  
  * @since 1.0
 38  
  */
 39  35
 public class Ini implements Map<String, Ini.Section> {
 40  
 
 41  1
     private static transient final Logger log = LoggerFactory.getLogger(Ini.class);
 42  
 
 43  
     public static final String DEFAULT_SECTION_NAME = ""; //empty string means the first unnamed section
 44  
     public static final String DEFAULT_CHARSET_NAME = "UTF-8";
 45  
 
 46  
     public static final String COMMENT_POUND = "#";
 47  
     public static final String COMMENT_SEMICOLON = ";";
 48  
     public static final String SECTION_PREFIX = "[";
 49  
     public static final String SECTION_SUFFIX = "]";
 50  
 
 51  
     protected static final char ESCAPE_TOKEN = '\\';
 52  
 
 53  
     private final Map<String, Section> sections;
 54  
 
 55  
     /**
 56  
      * Creates a new empty {@code Ini} instance.
 57  
      */
 58  39
     public Ini() {
 59  39
         this.sections = new LinkedHashMap<String, Section>();
 60  39
     }
 61  
 
 62  
     /**
 63  
      * Creates a new {@code Ini} instance with the specified defaults.
 64  
      *
 65  
      * @param defaults the default sections and/or key-value pairs to copy into the new instance.
 66  
      */
 67  
     public Ini(Ini defaults) {
 68  0
         this();
 69  0
         if (defaults == null) {
 70  0
             throw new NullPointerException("Defaults cannot be null.");
 71  
         }
 72  0
         for (Section section : defaults.getSections()) {
 73  0
             Section copy = new Section(section);
 74  0
             this.sections.put(section.getName(), copy);
 75  0
         }
 76  0
     }
 77  
 
 78  
     /**
 79  
      * Returns {@code true} if no sections have been configured, or if there are sections, but the sections themselves
 80  
      * are all empty, {@code false} otherwise.
 81  
      *
 82  
      * @return {@code true} if no sections have been configured, or if there are sections, but the sections themselves
 83  
      *         are all empty, {@code false} otherwise.
 84  
      */
 85  
     public boolean isEmpty() {
 86  124
         Collection<Section> sections = this.sections.values();
 87  124
         if (!sections.isEmpty()) {
 88  122
             for (Section section : sections) {
 89  122
                 if (!section.isEmpty()) {
 90  122
                     return false;
 91  
                 }
 92  0
             }
 93  
         }
 94  2
         return true;
 95  
     }
 96  
 
 97  
     /**
 98  
      * Returns the names of all sections managed by this {@code Ini} instance or an empty collection if there are
 99  
      * no sections.
 100  
      *
 101  
      * @return the names of all sections managed by this {@code Ini} instance or an empty collection if there are
 102  
      *         no sections.
 103  
      */
 104  
     public Set<String> getSectionNames() {
 105  0
         return Collections.unmodifiableSet(sections.keySet());
 106  
     }
 107  
 
 108  
     /**
 109  
      * Returns the sections managed by this {@code Ini} instance or an empty collection if there are
 110  
      * no sections.
 111  
      *
 112  
      * @return the sections managed by this {@code Ini} instance or an empty collection if there are
 113  
      *         no sections.
 114  
      */
 115  
     public Collection<Section> getSections() {
 116  6
         return Collections.unmodifiableCollection(sections.values());
 117  
     }
 118  
 
 119  
     /**
 120  
      * Returns the {@link Section} with the given name or {@code null} if no section with that name exists.
 121  
      *
 122  
      * @param sectionName the name of the section to retrieve.
 123  
      * @return the {@link Section} with the given name or {@code null} if no section with that name exists.
 124  
      */
 125  
     public Section getSection(String sectionName) {
 126  189
         String name = cleanName(sectionName);
 127  189
         return sections.get(name);
 128  
     }
 129  
 
 130  
     /**
 131  
      * Ensures a section with the specified name exists, adding a new one if it does not yet exist.
 132  
      *
 133  
      * @param sectionName the name of the section to ensure existence
 134  
      * @return the section created if it did not yet exist, or the existing Section that already existed.
 135  
      */
 136  
     public Section addSection(String sectionName) {
 137  30
         String name = cleanName(sectionName);
 138  30
         Section section = getSection(name);
 139  30
         if (section == null) {
 140  30
             section = new Section(name);
 141  30
             this.sections.put(name, section);
 142  
         }
 143  30
         return section;
 144  
     }
 145  
 
 146  
     /**
 147  
      * Removes the section with the specified name and returns it, or {@code null} if the section did not exist.
 148  
      *
 149  
      * @param sectionName the name of the section to remove.
 150  
      * @return the section with the specified name or {@code null} if the section did not exist.
 151  
      */
 152  
     public Section removeSection(String sectionName) {
 153  0
         String name = cleanName(sectionName);
 154  0
         return this.sections.remove(name);
 155  
     }
 156  
 
 157  
     private static String cleanName(String sectionName) {
 158  283
         String name = StringUtils.clean(sectionName);
 159  283
         if (name == null) {
 160  4
             log.trace("Specified name was null or empty.  Defaulting to the default section (name = \"\")");
 161  4
             name = DEFAULT_SECTION_NAME;
 162  
         }
 163  283
         return name;
 164  
     }
 165  
 
 166  
     /**
 167  
      * Sets a name/value pair for the section with the given {@code sectionName}.  If the section does not yet exist,
 168  
      * it will be created.  If the {@code sectionName} is null or empty, the name/value pair will be placed in the
 169  
      * default (unnamed, empty string) section.
 170  
      *
 171  
      * @param sectionName   the name of the section to add the name/value pair
 172  
      * @param propertyName  the name of the property to add
 173  
      * @param propertyValue the property value
 174  
      */
 175  
     public void setSectionProperty(String sectionName, String propertyName, String propertyValue) {
 176  53
         String name = cleanName(sectionName);
 177  53
         Section section = getSection(name);
 178  53
         if (section == null) {
 179  20
             section = addSection(name);
 180  
         }
 181  53
         section.put(propertyName, propertyValue);
 182  53
     }
 183  
 
 184  
     /**
 185  
      * Returns the value of the specified section property, or {@code null} if the section or property do not exist.
 186  
      *
 187  
      * @param sectionName  the name of the section to retrieve to acquire the property value
 188  
      * @param propertyName the name of the section property for which to return the value
 189  
      * @return the value of the specified section property, or {@code null} if the section or property do not exist.
 190  
      */
 191  
     public String getSectionProperty(String sectionName, String propertyName) {
 192  0
         Section section = getSection(sectionName);
 193  0
         return section != null ? section.get(propertyName) : null;
 194  
     }
 195  
 
 196  
     /**
 197  
      * Returns the value of the specified section property, or the {@code defaultValue} if the section or
 198  
      * property do not exist.
 199  
      *
 200  
      * @param sectionName  the name of the section to add the name/value pair
 201  
      * @param propertyName the name of the property to add
 202  
      * @param defaultValue the default value to return if the section or property do not exist.
 203  
      * @return the value of the specified section property, or the {@code defaultValue} if the section or
 204  
      *         property do not exist.
 205  
      */
 206  
     public String getSectionProperty(String sectionName, String propertyName, String defaultValue) {
 207  0
         String value = getSectionProperty(sectionName, propertyName);
 208  0
         return value != null ? value : defaultValue;
 209  
     }
 210  
 
 211  
     /**
 212  
      * Creates a new {@code Ini} instance loaded with the INI-formatted data in the resource at the given path.  The
 213  
      * resource path may be any value interpretable by the
 214  
      * {@link ResourceUtils#getInputStreamForPath(String) ResourceUtils.getInputStreamForPath} method.
 215  
      *
 216  
      * @param resourcePath the resource location of the INI data to load when creating the {@code Ini} instance.
 217  
      * @return a new {@code Ini} instance loaded with the INI-formatted data in the resource at the given path.
 218  
      * @throws ConfigurationException if the path cannot be loaded into an {@code Ini} instance.
 219  
      */
 220  
     public static Ini fromResourcePath(String resourcePath) throws ConfigurationException {
 221  4
         if (!StringUtils.hasLength(resourcePath)) {
 222  0
             throw new IllegalArgumentException("Resource Path argument cannot be null or empty.");
 223  
         }
 224  4
         Ini ini = new Ini();
 225  4
         ini.loadFromPath(resourcePath);
 226  4
         return ini;
 227  
     }
 228  
 
 229  
     /**
 230  
      * Loads data from the specified resource path into this current {@code Ini} instance.  The
 231  
      * resource path may be any value interpretable by the
 232  
      * {@link ResourceUtils#getInputStreamForPath(String) ResourceUtils.getInputStreamForPath} method.
 233  
      *
 234  
      * @param resourcePath the resource location of the INI data to load into this instance.
 235  
      * @throws ConfigurationException if the path cannot be loaded
 236  
      */
 237  
     public void loadFromPath(String resourcePath) throws ConfigurationException {
 238  
         InputStream is;
 239  
         try {
 240  4
             is = ResourceUtils.getInputStreamForPath(resourcePath);
 241  0
         } catch (IOException e) {
 242  0
             throw new ConfigurationException(e);
 243  4
         }
 244  4
         load(is);
 245  4
     }
 246  
 
 247  
     /**
 248  
      * Loads the specified raw INI-formatted text into this instance.
 249  
      *
 250  
      * @param iniConfig the raw INI-formatted text to load into this instance.
 251  
      * @throws ConfigurationException if the text cannot be loaded
 252  
      */
 253  
     public void load(String iniConfig) throws ConfigurationException {
 254  4
         load(new Scanner(iniConfig));
 255  4
     }
 256  
 
 257  
     /**
 258  
      * Loads the INI-formatted text backed by the given InputStream into this instance.  This implementation will
 259  
      * close the input stream after it has finished loading.  It is expected that the stream's contents are
 260  
      * UTF-8 encoded.
 261  
      *
 262  
      * @param is the {@code InputStream} from which to read the INI-formatted text
 263  
      * @throws ConfigurationException if unable
 264  
      */
 265  
     public void load(InputStream is) throws ConfigurationException {
 266  4
         if (is == null) {
 267  0
             throw new NullPointerException("InputStream argument cannot be null.");
 268  
         }
 269  
         InputStreamReader isr;
 270  
         try {
 271  4
             isr = new InputStreamReader(is, DEFAULT_CHARSET_NAME);
 272  0
         } catch (UnsupportedEncodingException e) {
 273  0
             throw new ConfigurationException(e);
 274  4
         }
 275  4
         load(isr);
 276  4
     }
 277  
 
 278  
     /**
 279  
      * Loads the INI-formatted text backed by the given Reader into this instance.  This implementation will close the
 280  
      * reader after it has finished loading.
 281  
      *
 282  
      * @param reader the {@code Reader} from which to read the INI-formatted text
 283  
      */
 284  
     public void load(Reader reader) {
 285  4
         Scanner scanner = new Scanner(reader);
 286  
         try {
 287  4
             load(scanner);
 288  
         } finally {
 289  0
             try {
 290  4
                 scanner.close();
 291  0
             } catch (Exception e) {
 292  0
                 log.debug("Unable to cleanly close the InputStream scanner.  Non-critical - ignoring.", e);
 293  4
             }
 294  0
         }
 295  4
     }
 296  
 
 297  
     private void addSection(String name, StringBuilder content) {
 298  20
         if (content.length() > 0) {
 299  13
             String contentString = content.toString();
 300  13
             String cleaned = StringUtils.clean(contentString);
 301  13
             if (cleaned != null) {
 302  13
                 Section section = new Section(name, contentString);
 303  13
                 if (!section.isEmpty()) {
 304  13
                     sections.put(name, section);
 305  
                 }
 306  
             }
 307  
         }
 308  20
     }
 309  
 
 310  
     /**
 311  
      * Loads the INI-formatted text backed by the given Scanner.  This implementation will close the
 312  
      * scanner after it has finished loading.
 313  
      *
 314  
      * @param scanner the {@code Scanner} from which to read the INI-formatted text
 315  
      */
 316  
     public void load(Scanner scanner) {
 317  
 
 318  9
         String sectionName = DEFAULT_SECTION_NAME;
 319  9
         StringBuilder sectionContent = new StringBuilder();
 320  
 
 321  146
         while (scanner.hasNextLine()) {
 322  
 
 323  137
             String rawLine = scanner.nextLine();
 324  137
             String line = StringUtils.clean(rawLine);
 325  
 
 326  137
             if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {
 327  
                 //skip empty lines and comments:
 328  2
                 continue;
 329  
             }
 330  
 
 331  38
             String newSectionName = getSectionName(line);
 332  38
             if (newSectionName != null) {
 333  
                 //found a new section - convert the currently buffered one into a Section object
 334  11
                 addSection(sectionName, sectionContent);
 335  
 
 336  
                 //reset the buffer for the new section:
 337  11
                 sectionContent = new StringBuilder();
 338  
 
 339  11
                 sectionName = newSectionName;
 340  
 
 341  11
                 if (log.isDebugEnabled()) {
 342  11
                     log.debug("Parsing " + SECTION_PREFIX + sectionName + SECTION_SUFFIX);
 343  
                 }
 344  
             } else {
 345  
                 //normal line - add it to the existing content buffer:
 346  27
                 sectionContent.append(rawLine).append("\n");
 347  
             }
 348  38
         }
 349  
 
 350  
         //finish any remaining buffered content:
 351  9
         addSection(sectionName, sectionContent);
 352  9
     }
 353  
 
 354  
     protected static boolean isSectionHeader(String line) {
 355  38
         String s = StringUtils.clean(line);
 356  38
         return s != null && s.startsWith(SECTION_PREFIX) && s.endsWith(SECTION_SUFFIX);
 357  
     }
 358  
 
 359  
     protected static String getSectionName(String line) {
 360  38
         String s = StringUtils.clean(line);
 361  38
         if (isSectionHeader(s)) {
 362  11
             return cleanName(s.substring(1, s.length() - 1));
 363  
         }
 364  27
         return null;
 365  
     }
 366  
 
 367  
     public boolean equals(Object obj) {
 368  0
         if (obj instanceof Ini) {
 369  0
             Ini ini = (Ini) obj;
 370  0
             return this.sections.equals(ini.sections);
 371  
         }
 372  0
         return false;
 373  
     }
 374  
 
 375  
     @Override
 376  
     public int hashCode() {
 377  0
         return this.sections.hashCode();
 378  
     }
 379  
 
 380  
     public String toString() {
 381  21
         if (CollectionUtils.isEmpty(this.sections)) {
 382  0
             return "<empty INI>";
 383  
         } else {
 384  21
             StringBuilder sb = new StringBuilder("sections=");
 385  21
             int i = 0;
 386  21
             for (Ini.Section section : this.sections.values()) {
 387  26
                 if (i > 0) {
 388  5
                     sb.append(",");
 389  
                 }
 390  26
                 sb.append(section.toString());
 391  26
                 i++;
 392  26
             }
 393  21
             return sb.toString();
 394  
         }
 395  
     }
 396  
 
 397  
     public int size() {
 398  0
         return this.sections.size();
 399  
     }
 400  
 
 401  
     public boolean containsKey(Object key) {
 402  0
         return this.sections.containsKey(key);
 403  
     }
 404  
 
 405  
     public boolean containsValue(Object value) {
 406  0
         return this.sections.containsValue(value);
 407  
     }
 408  
 
 409  
     public Section get(Object key) {
 410  0
         return this.sections.get(key);
 411  
     }
 412  
 
 413  
     public Section put(String key, Section value) {
 414  0
         return this.sections.put(key, value);
 415  
     }
 416  
 
 417  
     public Section remove(Object key) {
 418  0
         return this.sections.remove(key);
 419  
     }
 420  
 
 421  
     public void putAll(Map<? extends String, ? extends Section> m) {
 422  0
         this.sections.putAll(m);
 423  0
     }
 424  
 
 425  
     public void clear() {
 426  0
         this.sections.clear();
 427  0
     }
 428  
 
 429  
     public Set<String> keySet() {
 430  0
         return Collections.unmodifiableSet(this.sections.keySet());
 431  
     }
 432  
 
 433  
     public Collection<Section> values() {
 434  0
         return Collections.unmodifiableCollection(this.sections.values());
 435  
     }
 436  
 
 437  
     public Set<Entry<String, Section>> entrySet() {
 438  0
         return Collections.unmodifiableSet(this.sections.entrySet());
 439  
     }
 440  
 
 441  
     /**
 442  
      * An {@code Ini.Section} is String-key-to-String-value Map, identifiable by a
 443  
      * {@link #getName() name} unique within an {@link Ini} instance.
 444  
      */
 445  71
     public static class Section implements Map<String, String> {
 446  
         private final String name;
 447  
         private final Map<String, String> props;
 448  
 
 449  30
         private Section(String name) {
 450  30
             if (name == null) {
 451  0
                 throw new NullPointerException("name");
 452  
             }
 453  30
             this.name = name;
 454  30
             this.props = new LinkedHashMap<String, String>();
 455  30
         }
 456  
 
 457  13
         private Section(String name, String sectionContent) {
 458  13
             if (name == null) {
 459  0
                 throw new NullPointerException("name");
 460  
             }
 461  13
             this.name = name;
 462  
             Map<String,String> props;
 463  13
             if (StringUtils.hasText(sectionContent) ) {
 464  13
                 props = toMapProps(sectionContent);
 465  
             } else {
 466  0
                 props = new LinkedHashMap<String,String>();
 467  
             }
 468  13
             if ( props != null ) {
 469  13
                 this.props = props;
 470  
             } else {
 471  0
                 this.props = new LinkedHashMap<String,String>();
 472  
             }
 473  13
         }
 474  
 
 475  
         private Section(Section defaults) {
 476  0
             this(defaults.getName());
 477  0
             putAll(defaults.props);
 478  0
         }
 479  
 
 480  
         //Protected to access in a test case - NOT considered part of Shiro's public API
 481  
 
 482  
         protected static boolean isContinued(String line) {
 483  31
             if (!StringUtils.hasText(line)) {
 484  0
                 return false;
 485  
             }
 486  31
             int length = line.length();
 487  
             //find the number of backslashes at the end of the line.  If an even number, the
 488  
             //backslashes are considered escaped.  If an odd number, the line is considered continued on the next line
 489  31
             int backslashCount = 0;
 490  38
             for (int i = length - 1; i > 0; i--) {
 491  38
                 if (line.charAt(i) == ESCAPE_TOKEN) {
 492  7
                     backslashCount++;
 493  
                 } else {
 494  
                     break;
 495  
                 }
 496  
             }
 497  31
             return backslashCount % 2 != 0;
 498  
         }
 499  
 
 500  
         private static boolean isKeyValueSeparatorChar(char c) {
 501  627
             return Character.isWhitespace(c) || c == ':' || c == '=';
 502  
         }
 503  
 
 504  
         private static boolean isCharEscaped(CharSequence s, int index) {
 505  97
             return index > 0 && s.charAt(index - 1) == ESCAPE_TOKEN;
 506  
         }
 507  
 
 508  
         //Protected to access in a test case - NOT considered part of Shiro's public API
 509  
         protected static String[] splitKeyValue(String keyValueLine) {
 510  36
             String line = StringUtils.clean(keyValueLine);
 511  36
             if (line == null) {
 512  0
                 return null;
 513  
             }
 514  36
             StringBuilder keyBuffer = new StringBuilder();
 515  36
             StringBuilder valueBuffer = new StringBuilder();
 516  
 
 517  36
             boolean buildingKey = true; //we'll build the value next:
 518  
 
 519  1040
             for (int i = 0; i < line.length(); i++) {
 520  1004
                 char c = line.charAt(i);
 521  
 
 522  1004
                 if (buildingKey) {
 523  530
                     if (isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) {
 524  35
                         buildingKey = false;//now start building the value
 525  
                     } else {
 526  495
                         keyBuffer.append(c);
 527  
                     }
 528  
                 } else {
 529  474
                     if (valueBuffer.length() == 0 && isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) {
 530  
                         //swallow the separator chars before we start building the value
 531  
                     } else {
 532  412
                         valueBuffer.append(c);
 533  
                     }
 534  
                 }
 535  
             }
 536  
 
 537  36
             String key = StringUtils.clean(keyBuffer.toString());
 538  36
             String value = StringUtils.clean(valueBuffer.toString());
 539  
 
 540  36
             if (key == null || value == null) {
 541  1
                 String msg = "Line argument must contain a key and a value.  Only one string token was found.";
 542  1
                 throw new IllegalArgumentException(msg);
 543  
             }
 544  
 
 545  35
             log.trace("Discovered key/value pair: {}={}", key, value);
 546  
 
 547  35
             return new String[]{key, value};
 548  
         }
 549  
 
 550  
         private static Map<String, String> toMapProps(String content) {
 551  13
             Map<String, String> props = new LinkedHashMap<String, String>();
 552  
             String line;
 553  13
             StringBuilder lineBuffer = new StringBuilder();
 554  13
             Scanner scanner = new Scanner(content);
 555  40
             while (scanner.hasNextLine()) {
 556  27
                 line = StringUtils.clean(scanner.nextLine());
 557  27
                 if (isContinued(line)) {
 558  
                     //strip off the last continuation backslash:
 559  1
                     line = line.substring(0, line.length() - 1);
 560  1
                     lineBuffer.append(line);
 561  1
                     continue;
 562  
                 } else {
 563  26
                     lineBuffer.append(line);
 564  
                 }
 565  26
                 line = lineBuffer.toString();
 566  26
                 lineBuffer = new StringBuilder();
 567  26
                 String[] kvPair = splitKeyValue(line);
 568  26
                 props.put(kvPair[0], kvPair[1]);
 569  26
             }
 570  
 
 571  13
             return props;
 572  
         }
 573  
 
 574  
         public String getName() {
 575  28
             return this.name;
 576  
         }
 577  
 
 578  
         public void clear() {
 579  0
             this.props.clear();
 580  0
         }
 581  
 
 582  
         public boolean containsKey(Object key) {
 583  0
             return this.props.containsKey(key);
 584  
         }
 585  
 
 586  
         public boolean containsValue(Object value) {
 587  0
             return this.props.containsValue(value);
 588  
         }
 589  
 
 590  
         public Set<Entry<String, String>> entrySet() {
 591  18
             return this.props.entrySet();
 592  
         }
 593  
 
 594  
         public String get(Object key) {
 595  35
             return this.props.get(key);
 596  
         }
 597  
 
 598  
         public boolean isEmpty() {
 599  225
             return this.props.isEmpty();
 600  
         }
 601  
 
 602  
         public Set<String> keySet() {
 603  23
             return this.props.keySet();
 604  
         }
 605  
 
 606  
         public String put(String key, String value) {
 607  73
             return this.props.put(key, value);
 608  
         }
 609  
 
 610  
         public void putAll(Map<? extends String, ? extends String> m) {
 611  0
             this.props.putAll(m);
 612  0
         }
 613  
 
 614  
         public String remove(Object key) {
 615  0
             return this.props.remove(key);
 616  
         }
 617  
 
 618  
         public int size() {
 619  2
             return this.props.size();
 620  
         }
 621  
 
 622  
         public Collection<String> values() {
 623  0
             return this.props.values();
 624  
         }
 625  
 
 626  
         public String toString() {
 627  26
             String name = getName();
 628  26
             if (DEFAULT_SECTION_NAME.equals(name)) {
 629  0
                 return "<default>";
 630  
             }
 631  26
             return name;
 632  
         }
 633  
 
 634  
         @Override
 635  
         public boolean equals(Object obj) {
 636  0
             if (obj instanceof Section) {
 637  0
                 Section other = (Section) obj;
 638  0
                 return getName().equals(other.getName()) && this.props.equals(other.props);
 639  
             }
 640  0
             return false;
 641  
         }
 642  
 
 643  
         @Override
 644  
         public int hashCode() {
 645  0
             return this.name.hashCode() * 31 + this.props.hashCode();
 646  
         }
 647  
     }
 648  
 
 649  
 }