<commit_before>/*
 * Copyright (C) 2004-2008 Jive Software, 2022 Ignite Realtime Foundation. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.util;

import org.apache.commons.text.StringEscapeUtils;
import org.dom4j.*;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Provides the the ability to use simple XML property files. Each property is
 * in the form X.Y.Z, which would map to an XML snippet of:
 * <pre>
 * &lt;X&gt;
 *     &lt;Y&gt;
 *         &lt;Z&gt;someValue&lt;/Z&gt;
 *     &lt;/Y&gt;
 * &lt;/X&gt;
 * </pre>
 * The XML file is passed in to the constructor and must be readable and
 * writable. Setting property values will automatically persist those value
 * to disk. The file encoding used is UTF-8.
 *
 * @author <NAME>
 * @author <NAME>
 */
public class XMLProperties {

    private static final Logger Log = LoggerFactory.getLogger(XMLProperties.class);
    private static final String ENCRYPTED_ATTRIBUTE = "encrypted";

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    private final Path file;
    private final Document document;

    /**
     * Parsing the XML file every time we need a property is slow. Therefore,
     * we use a Map to cache property values that are accessed more than once.
     */
    private final Map<String, Optional<String>> propertyCache = new HashMap<>();

    /**
     * Creates a new empty XMLPropertiesTest object.
     *
     * @throws IOException if an error occurs loading the properties.
     */
    public XMLProperties() throws IOException {
        file = null;
        document = buildDoc(new StringReader("<root />"));
    }

    /**
     * Creates a new XMLPropertiesTest object.
     *
     * @param fileName the full path the file that properties should be read from
     *                 and written to.
     * @throws IOException if an error occurs loading the properties.
     */
    public XMLProperties(String fileName) throws IOException {
        this(Paths.get(fileName));
    }

    /**
     * Loads XML properties from a stream.
     *
     * @param in the input stream of XML.
     * @throws IOException if an exception occurs when reading the stream.
     */
    public XMLProperties(InputStream in) throws IOException {
        file = null;
        try (Reader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
            document = buildDoc(reader);
        }
    }

    /**
     * Creates a new XMLPropertiesTest object.
     *
     * @param file the file that properties should be read from and written to.
     * @throws IOException if an error occurs loading the properties.
     */
    @Deprecated
    public XMLProperties(File file) throws IOException {
        this(file.toPath());
    }

    /**
     * Creates a new XMLPropertiesTest object.
     *
     * @param file the file that properties should be read from and written to.
     * @throws IOException if an error occurs loading the properties.
     */
    public XMLProperties(Path file) throws IOException {
        this.file = file;
        if (Files.notExists(file)) {
            // Attempt to recover from this error case by seeing if the
            // tmp file exists. It's possible that the rename of the
            // tmp file failed the last time Jive was running,
            // but that it exists now.
            Path tempFile;
            tempFile = file.getParent().resolve(file.getFileName() + ".tmp");
            if (Files.exists(tempFile)) {
                Log.error("WARNING: " + file.getFileName() + " was not found, but temp file from " +
                        "previous write operation was. Attempting automatic recovery." +
                        " Please check file for data consistency.");
                Files.move(tempFile, file, StandardCopyOption.REPLACE_EXISTING);
            }
            // There isn't a possible way to recover from the file not
            // being there, so throw an error.
            else {
                throw new NoSuchFileException("XML properties file does not exist: "
                        + file.getFileName());
            }
        }
        // Check read and write privs.
        if (!Files.isReadable(file)) {
            throw new IOException("XML properties file must be readable: " + file.getFileName());
        }
        if (!Files.isWritable(file)) {
            throw new IOException("XML properties file must be writable: " + file.getFileName());
        }

        try (Reader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
             document = buildDoc(reader);
        }
    }

    /**
     * Returns the value of the specified property.
     *
     * @param name the name of the property to get.
     * @return the value of the specified property.
     */
    public String getProperty(String name) {
        return getProperty(name, true);
    }

    /**
     * Returns the value of the specified property.
     *
     * @param name the name of the property to get.
     * @param ignoreEmpty Ignore empty property values (return null)
     * @return the value of the specified property.
     */
    public synchronized String getProperty(String name, boolean ignoreEmpty) {
        final Lock readLock = readWriteLock.readLock();
        readLock.lock();

        String value = null;
        boolean mustRewrite = false;
        try {
            if (propertyCache.containsKey(name)) {
                return propertyCache.get(name).orElse(null);
            }

            String[] propName = parsePropertyName(name);
            // Search for this property by traversing down the XML hierarchy.
            Element element = document.getRootElement();
            for (String aPropName : propName) {
                element = element.element(aPropName);
                if (element == null) {
                    // This node doesn't match this part of the property name which
                    // indicates this property doesn't exist so return null.
                    propertyCache.put(name, Optional.empty());
                    return null;
                }
            }
            // At this point, we found a matching property, so return its value.
            // Empty strings are returned as null.
            value = element.getTextTrim();<commit_msg>Re-order locking to happen immediately before 'try'<commit_after>/*
 * Copyright (C) 2004-2008 Jive Software, 2022 Ignite Realtime Foundation. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.util;

import org.apache.commons.text.StringEscapeUtils;
import org.dom4j.*;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Provides the the ability to use simple XML property files. Each property is
 * in the form X.Y.Z, which would map to an XML snippet of:
 * <pre>
 * &lt;X&gt;
 *     &lt;Y&gt;
 *         &lt;Z&gt;someValue&lt;/Z&gt;
 *     &lt;/Y&gt;
 * &lt;/X&gt;
 * </pre>
 * The XML file is passed in to the constructor and must be readable and
 * writable. Setting property values will automatically persist those value
 * to disk. The file encoding used is UTF-8.
 *
 * @author <NAME>
 * @author <NAME>
 */
public class XMLProperties {

    private static final Logger Log = LoggerFactory.getLogger(XMLProperties.class);
    private static final String ENCRYPTED_ATTRIBUTE = "encrypted";

    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    private final Path file;
    private final Document document;

    /**
     * Parsing the XML file every time we need a property is slow. Therefore,
     * we use a Map to cache property values that are accessed more than once.
     */
    private final Map<String, Optional<String>> propertyCache = new HashMap<>();

    /**
     * Creates a new empty XMLPropertiesTest object.
     *
     * @throws IOException if an error occurs loading the properties.
     */
    public XMLProperties() throws IOException {
        file = null;
        document = buildDoc(new StringReader("<root />"));
    }

    /**
     * Creates a new XMLPropertiesTest object.
     *
     * @param fileName the full path the file that properties should be read from
     *                 and written to.
     * @throws IOException if an error occurs loading the properties.
     */
    public XMLProperties(String fileName) throws IOException {
        this(Paths.get(fileName));
    }

    /**
     * Loads XML properties from a stream.
     *
     * @param in the input stream of XML.
     * @throws IOException if an exception occurs when reading the stream.
     */
    public XMLProperties(InputStream in) throws IOException {
        file = null;
        try (Reader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
            document = buildDoc(reader);
        }
    }

    /**
     * Creates a new XMLPropertiesTest object.
     *
     * @param file the file that properties should be read from and written to.
     * @throws IOException if an error occurs loading the properties.
     */
    @Deprecated
    public XMLProperties(File file) throws IOException {
        this(file.toPath());
    }

    /**
     * Creates a new XMLPropertiesTest object.
     *
     * @param file the file that properties should be read from and written to.
     * @throws IOException if an error occurs loading the properties.
     */
    public XMLProperties(Path file) throws IOException {
        this.file = file;
        if (Files.notExists(file)) {
            // Attempt to recover from this error case by seeing if the
            // tmp file exists. It's possible that the rename of the
            // tmp file failed the last time Jive was running,
            // but that it exists now.
            Path tempFile;
            tempFile = file.getParent().resolve(file.getFileName() + ".tmp");
            if (Files.exists(tempFile)) {
                Log.error("WARNING: " + file.getFileName() + " was not found, but temp file from " +
                        "previous write operation was. Attempting automatic recovery." +
                        " Please check file for data consistency.");
                Files.move(tempFile, file, StandardCopyOption.REPLACE_EXISTING);
            }
            // There isn't a possible way to recover from the file not
            // being there, so throw an error.
            else {
                throw new NoSuchFileException("XML properties file does not exist: "
                        + file.getFileName());
            }
        }
        // Check read and write privs.
        if (!Files.isReadable(file)) {
            throw new IOException("XML properties file must be readable: " + file.getFileName());
        }
        if (!Files.isWritable(file)) {
            throw new IOException("XML properties file must be writable: " + file.getFileName());
        }

        try (Reader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
             document = buildDoc(reader);
        }
    }

    /**
     * Returns the value of the specified property.
     *
     * @param name the name of the property to get.
     * @return the value of the specified property.
     */
    public String getProperty(String name) {
        return getProperty(name, true);
    }

    /**
     * Returns the value of the specified property.
     *
     * @param name the name of the property to get.
     * @param ignoreEmpty Ignore empty property values (return null)
     * @return the value of the specified property.
     */
    public synchronized String getProperty(String name, boolean ignoreEmpty) {
        String value = null;
        boolean mustRewrite = false;

        final Lock readLock = readWriteLock.readLock();
        readLock.lock();
        try {
            if (propertyCache.containsKey(name)) {
                return propertyCache.get(name).orElse(null);
            }

            String[] propName = parsePropertyName(name);
            // Search for this property by traversing down the XML hierarchy.
            Element element = document.getRootElement();
            for (String aPropName : propName) {
                element = element.element(aPropName);
                if (element == null) {
                    // This node doesn't match this part of the property name which
                    // indicates this property doesn't exist so return null.
                    propertyCache.put(name, Optional.empty());
                    return null;
                }
            }
            // At this point, we found a matching property, so return its value.
            // Empty strings are returned as null.
            value = element.getTextTrim();
{'fraction_non_alphanumeric': 0.08661581137309293, 'fraction_numerical': 0.00332871012482663, 'mean_word_length': 2.2879616963064295, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2892392', 'n_tokens_mistral': 3716, 'n_tokens_neox': 3596, 'n_words': 1401}