com.nesscomputing.mojo.numbers.PropertyCache.java Source code

Java tutorial

Introduction

Here is the source code for com.nesscomputing.mojo.numbers.PropertyCache.java

Source

/**
 * Copyright (C) 2011 Ness Computing, Inc.
 *
 * 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 com.nesscomputing.mojo.numbers;

import static java.lang.String.format;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;
import java.util.Properties;

import javax.annotation.Nonnull;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;

import com.nesscomputing.mojo.numbers.beans.AbstractDefinition;
import com.nesscomputing.mojo.numbers.beans.IWFCEnum;
import com.nesscomputing.mojo.numbers.util.Log;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import com.google.common.io.Closeables;

public class PropertyCache {
    private static final Log LOG = Log.findLog();

    /** Cache for properties files loaded from disk */
    private Map<File, PropertyCacheEntry> propFiles = Maps.newHashMap();

    private final Properties ephemeralProperties = new Properties();

    public ValueProvider getPropertyValue(final AbstractDefinition<?> definition) throws IOException {
        final Properties props = getProperties(definition);
        if (props == null) {
            final String propName = definition.getPropertyName();
            final String value = definition.getInitialValue();
            if (value != null) {
                ephemeralProperties.setProperty(propName, value);
            }
            return new ValueProvider.PropertyProvider(ephemeralProperties, propName);
        } else {
            return findCurrentValue(props, definition);
        }
    }

    @VisibleForTesting
    ValueProvider findCurrentValue(final Properties props, final AbstractDefinition<?> definition) {
        final String propName = definition.getPropertyName();
        final boolean hasProperty = props.containsKey(propName);

        final boolean createProperty = IWFCEnum.checkState(definition.getOnMissingProperty(), hasProperty,
                propName);

        if (hasProperty) {
            return new ValueProvider.PropertyProvider(props, propName);
        } else if (createProperty) {
            props.setProperty(propName, definition.getInitialValue());
            return new ValueProvider.PropertyProvider(props, propName);
        } else {
            return ValueProvider.NULL_PROVIDER;
        }
    }

    @VisibleForTesting
    Properties getProperties(final AbstractDefinition<?> definition) throws IOException {
        final File definitionPropertyFile = definition.getPropertyFile();

        // Ephemeral, so return null.
        if (definitionPropertyFile == null) {
            return null;
        }

        PropertyCacheEntry propertyCacheEntry;
        final File propertyFile = definitionPropertyFile.getCanonicalFile();

        // Throws an exception if the file must exist and does not.
        final boolean createFile = IWFCEnum.checkState(definition.getOnMissingFile(), propertyFile.exists(),
                definitionPropertyFile.getCanonicalPath());

        propertyCacheEntry = propFiles.get(propertyFile);

        if (propertyCacheEntry != null) {
            // If there is a cache hit, something either has loaded the file
            // or another property has already put in a creation order.
            // Make sure that if this number has a creation order it is obeyed.
            if (createFile) {
                propertyCacheEntry.doCreate();
            }
        } else {
            // Try loading or creating properties.
            final Properties props = new Properties();

            if (!propertyFile.exists()) {
                propertyCacheEntry = new PropertyCacheEntry(props, false, createFile); // does not exist
            } else {
                if (propertyFile.isFile() && propertyFile.canRead()) {
                    InputStream stream = null;
                    try {
                        stream = new FileInputStream(propertyFile);
                        props.load(stream);
                        propertyCacheEntry = new PropertyCacheEntry(props, true, createFile);
                        propFiles.put(propertyFile, propertyCacheEntry);
                    } finally {
                        Closeables.closeQuietly(stream);
                    }
                } else {
                    throw new IllegalStateException(
                            format("Can not load %s, not a file!", definitionPropertyFile.getCanonicalPath()));
                }
            }
        }

        return propertyCacheEntry.getProps();
    }

    public void persist() throws IOException {
        for (final Map.Entry<File, PropertyCacheEntry> propFile : propFiles.entrySet()) {
            final PropertyCacheEntry entry = propFile.getValue();
            final File file = propFile.getKey();
            if (entry.isExists() || entry.isCreate()) {
                Preconditions.checkNotNull(file, "no file defined, can not persist!");
                final File oldFile = new File(file.getCanonicalPath() + ".bak");

                if (entry.isExists()) {
                    Preconditions.checkState(file.exists(), "File %s should exist!", file.getCanonicalPath());
                    // unlink an old file if necessary
                    if (oldFile.exists()) {
                        Preconditions.checkState(oldFile.delete(), "Could not delete '%s'",
                                file.getCanonicalPath());
                    }
                }
                final File newFile = new File(file.getCanonicalPath() + ".new");
                OutputStream stream = null;
                try {
                    stream = new FileOutputStream(newFile);
                    entry.getProps().store(stream, "created by maven-numbers-plugin");
                } finally {
                    Closeables.closeQuietly(stream);
                }

                if (file.exists()) {
                    if (file.renameTo(oldFile)) {
                        if (!newFile.renameTo(file)) {
                            LOG.warn("Could not rename '%s' to '%s'!", newFile, file);
                        }
                    } else {
                        LOG.warn("Could not rename '%s' to '%s'!", file, oldFile);
                    }
                }
            }
        }
    }

    public static class PropertyCacheEntry {
        private final Properties props;

        private final boolean exists;

        private boolean create;

        PropertyCacheEntry(@Nonnull final Properties props, final boolean exists, final boolean create) {
            Preconditions.checkNotNull(props, "Properties element can not be null!");
            this.props = props;
            this.exists = exists;
            this.create = create;
        }

        public Properties getProps() {
            return props;
        }

        public boolean isExists() {
            return exists;
        }

        public boolean isCreate() {
            return create;
        }

        public void doCreate() {
            this.create = true;
        }

        @Override
        public boolean equals(final Object other) {
            if (!(other instanceof PropertyCacheEntry))
                return false;
            PropertyCacheEntry castOther = (PropertyCacheEntry) other;
            return new EqualsBuilder().append(props, castOther.props).append(exists, castOther.exists)
                    .append(create, castOther.create).isEquals();
        }

        @Override
        public int hashCode() {
            return new HashCodeBuilder().append(props).append(exists).append(create).toHashCode();
        }

        @Override
        public String toString() {
            return new ToStringBuilder(this).append("props", props).append("exists", exists)
                    .append("create", create).toString();
        }
    }
}