alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 *
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 *
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.spi.project.support.ant;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.modules.project.ant.FileChangeSupport;
import org.netbeans.modules.project.ant.FileChangeSupportEvent;
import org.netbeans.modules.project.ant.FileChangeSupportListener;
import org.netbeans.modules.project.ant.UserQuestionHandler;
import org.netbeans.spi.project.support.ant.AntProjectHelper;
import org.openide.ErrorManager;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.filesystems.FileUtil;
import org.openide.modules.InstalledFileLocator;
import org.openide.util.UserQuestionException;
import org.openide.util.Utilities;

/**
 * Manages the loaded property files for {@link AntProjectHelper}.
 * @author Jesse Glick
 */
final class ProjectProperties {
    
    /** Associated helper. */
    private final AntProjectHelper helper;
    
    /**
     * Properties loaded from metadata files on disk.
     * Keys are project-relative paths such as {@link #PROJECT_PROPERTIES_PATH}.
     * Values are loaded property providers.
     */
    private final Map/**/ properties = new HashMap();
    
    /** @see #getStockPropertyPreprovider */
    private PropertyProvider stockPropertyPreprovider = null;
    
    /** @see #getStandardPropertyEvaluator */
    private PropertyEvaluator standardPropertyEvaluator = null;
    
    /**
     * Create a project properties helper object.
     * @param helper the associated helper
     */
    public ProjectProperties(AntProjectHelper helper) {
        this.helper = helper;
    }
    
    /**
     * Get properties from a given path.
     * @param path the project-relative path
     * @return the applicable properties (created if empty; never null)
     */
    public EditableProperties getProperties(String path) {
        EditableProperties ep = getPP(path).getEditablePropertiesOrNull();
        if (ep != null) {
            return ep.cloneProperties();
        } else {
            return new EditableProperties(true);
        }
    }
    
    /**
     * Store properties in memory.
     * @param path the project-relative path
     * @param props the new properties, or null to remove the properties file
     * @return true if an actual change was made
     */
    public boolean putProperties(String path, EditableProperties props) {
        return getPP(path).put(props);
    }
    
    /**
     * Write cached properties to disk.
     * @param the project-relative path
     * @throws IOException if the file could not be written
     */
    public void write(String path) throws IOException {
        assert properties.containsKey(path);
        getPP(path).write();
    }
    
    /**
     * Make a property provider that loads from this file
     * and fires changes when it is written to (even in memory).
     */
    public PropertyProvider getPropertyProvider(String path) {
        return getPP(path);
    }
    
    private PP getPP(String path) {
        PP pp = (PP)properties.get(path);
        if (pp == null) {
            pp = new PP(path, helper);
            properties.put(path, pp);
        }
        return pp;
    }
    
    private static final class PP implements PropertyProvider, FileChangeSupportListener {
        
        // XXX lock any loaded property files while the project is modified, to prevent manual editing,
        // and reload any modified files if the project is unmodified

        private final String path;
        private final AntProjectHelper helper;
        private EditableProperties properties = null;
        private boolean loaded = false;
        private final List/**/ listeners = new ArrayList();
        private boolean writing = false;
        
        public PP(String path, AntProjectHelper helper) {
            this.path = path;
            this.helper = helper;
            FileChangeSupport.DEFAULT.addListener(this, new File(FileUtil.toFile(dir()), path.replace('/', File.separatorChar)));
        }
        
        private FileObject dir() {
            return helper.getProjectDirectory();
        }
        
        public EditableProperties getEditablePropertiesOrNull() {
            if (!loaded) {
                properties = null;
                FileObject fo = dir().getFileObject(path);
                if (fo != null) {
                    try {
                        EditableProperties p;
                        InputStream is = fo.getInputStream();
                        try {
                            p = new EditableProperties(true);
                            p.load(is);
                        } finally {
                            is.close();
                        }
                        properties = p;
                    } catch (IOException e) {
                        ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
                    }
                }
                loaded = true;
            }
            return properties;
        }
        
        public boolean put(EditableProperties nue) {
            loaded = true;
            boolean modifying = !Utilities.compareObjects(nue, properties);
            if (modifying) {
                if (nue != null) {
                    properties = nue.cloneProperties();
                } else {
                    properties = null;
                }
                fireChange();
            }
            return modifying;
        }
        
        public void write() throws IOException {
            assert loaded;
            final FileObject f = dir().getFileObject(path);
            assert !writing;
            writing = true;
            try {
                if (properties != null) {
                    // Supposed to create/modify the file.
                    // Need to use an atomic action - otherwise listeners will first
                    // receive an event that the file has been written to zero length
                    // (which for *.properties means no keys), which is wrong.
                    dir().getFileSystem().runAtomicAction(new FileSystem.AtomicAction() {
                        public void run() throws IOException {
                            final FileObject _f;
                            if (f == null) {
                                _f = FileUtil.createData(dir(), path);
                            } else {
                                _f = f;
                            }
                            final FileSystem.AtomicAction body = new FileSystem.AtomicAction() {
                                public void run() throws IOException {
                                    FileLock lock = _f.lock();
                                    try {
                                        OutputStream os = _f.getOutputStream(lock);
                                        try {
                                            properties.store(os);
                                        } finally {
                                            os.close();
                                        }
                                    } finally {
                                        lock.releaseLock();
                                    }
                                }
                            };
                            try {
                                body.run();
                            } catch (UserQuestionException uqe) { // #46089
                                UserQuestionHandler.handle(uqe, new UserQuestionHandler.Callback() {
                                    public void accepted() {
                                        // Try again.
                                        try {
                                            body.run();
                                        } catch (IOException e) {
                                            // Oh well.
                                            ErrorManager.getDefault().notify(e);
                                            reload();
                                        }
                                    }
                                    public void denied() {
                                        reload();
                                    }
                                    public void error(IOException e) {
                                        ErrorManager.getDefault().notify(e);
                                        reload();
                                    }
                                    private void reload() {
                                        // Revert the save.
                                        diskChange();
                                    }
                                });
                            }
                        }
                    });
                } else {
                    // We are supposed to remove any existing file.
                    if (f != null) {
                        f.delete();
                    }
                }
            } finally {
                writing = false;
            }
        }
        
        public Map getProperties() {
            Map/**/ props = getEditablePropertiesOrNull();
            if (props != null) {
                return Collections.unmodifiableMap(props);
            } else {
                return Collections.EMPTY_MAP;
            }
        }
        
        public synchronized void addChangeListener(ChangeListener l) {
            listeners.add(l);
        }
        
        public synchronized void removeChangeListener(ChangeListener l) {
            listeners.remove(l);
        }
        
        private void fireChange() {
            ChangeListener[] ls;
            synchronized (this) {
                if (listeners.isEmpty()) {
                    return;
                }
                ls = (ChangeListener[])listeners.toArray(new ChangeListener[listeners.size()]);
            }
            ChangeEvent ev = new ChangeEvent(this);
            for (int i = 0; i < ls.length; i++) {
                ls[i].stateChanged(ev);
            }
        }
        
        private void diskChange() {
            // XXX should check for a possible clobber from in-memory data
            if (!writing) {
                loaded = false;
            }
            fireChange();
            if (!writing) {
                helper.fireExternalChange(path);
            }
        }

        public void fileCreated(FileChangeSupportEvent event) {
            diskChange();
        }

        public void fileDeleted(FileChangeSupportEvent event) {
            diskChange();
        }

        public void fileModified(FileChangeSupportEvent event) {
            diskChange();
        }
        
    }

    /**
     * See {@link AntProjectHelper#getStockPropertyPreprovider}.
     */
    public PropertyProvider getStockPropertyPreprovider() {
        if (stockPropertyPreprovider == null) {
            Map/**/ m = new HashMap();
            Properties p = System.getProperties();
            synchronized (p) {
                Iterator it = p.entrySet().iterator();
                while (it.hasNext()) {
                    Map.Entry entry = (Map.Entry) it.next();
                    if (!(entry.getValue() instanceof String) || !(entry.getKey() instanceof String)) {
                        ErrorManager.getDefault().log(ErrorManager.WARNING, "WARNING: removing non-String-valued system property " + entry.getKey() + "=" + entry.getValue() + " (cf. #45788)");
                        it.remove();
                    }
                }
                m.putAll(p);
            }
            m.put("basedir", FileUtil.toFile(helper.getProjectDirectory()).getAbsolutePath()); // NOI18N
            File antJar = InstalledFileLocator.getDefault().locate("ant/lib/ant.jar", "org.apache.tools.ant.module", false); // NOI18N
            if (antJar != null) {
                File antHome = antJar.getParentFile().getParentFile();
                m.put("ant.home", antHome.getAbsolutePath()); // NOI18N
            }
            stockPropertyPreprovider = PropertyUtils.fixedPropertyProvider(m);
        }
        return stockPropertyPreprovider;
    }
    
    /**
     * See {@link AntProjectHelper#getStandardPropertyEvaluator}.
     */
    public PropertyEvaluator getStandardPropertyEvaluator() {
        if (standardPropertyEvaluator == null) {
            PropertyEvaluator findUserPropertiesFile = PropertyUtils.sequentialPropertyEvaluator(
                getStockPropertyPreprovider(),
                new PropertyProvider[] {
                    getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH),
                }
            );
            PropertyProvider globalProperties = new UserPropertiesProvider(findUserPropertiesFile);
            standardPropertyEvaluator = PropertyUtils.sequentialPropertyEvaluator(
                getStockPropertyPreprovider(),
                new PropertyProvider[] {
                    getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH),
                    globalProperties,
                    getPropertyProvider(AntProjectHelper.PROJECT_PROPERTIES_PATH),
                }
            );
        }
        return standardPropertyEvaluator;
    }
    private PropertyProvider computeDelegate(PropertyEvaluator findUserPropertiesFile) {
        String userPropertiesFile = findUserPropertiesFile.getProperty("user.properties.file"); // NOI18N
        if (userPropertiesFile != null) {
            // Have some defined global properties file, so read it and listen to changes in it.
            File f = helper.resolveFile(userPropertiesFile);
            if (f.equals(PropertyUtils.USER_BUILD_PROPERTIES)) {
                // Just to share the cache.
                return PropertyUtils.globalPropertyProvider();
            } else {
                return PropertyUtils.propertiesFilePropertyProvider(f);
            }
        } else {
            // Use the in-IDE default.
            return PropertyUtils.globalPropertyProvider();
        }
    }
    private final class UserPropertiesProvider extends PropertyUtils.DelegatingPropertyProvider implements PropertyChangeListener {
        private final PropertyEvaluator findUserPropertiesFile;
        public UserPropertiesProvider(PropertyEvaluator findUserPropertiesFile) {
            super(computeDelegate(findUserPropertiesFile));
            this.findUserPropertiesFile = findUserPropertiesFile;
            findUserPropertiesFile.addPropertyChangeListener(this);
        }
        public void propertyChange(PropertyChangeEvent ev) {
            if ("user.properties.file".equals(ev.getPropertyName())) { // NOI18N
                setDelegate(computeDelegate(findUserPropertiesFile));
            }
        }
    }
    
}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.