|
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.modules.project.ant.FileChangeSupport;
import org.netbeans.modules.project.ant.FileChangeSupportEvent;
import org.netbeans.modules.project.ant.FileChangeSupportListener;
import org.openide.ErrorManager;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Mutex;
import org.openide.util.MutexException;
import org.openide.util.TopologicalSortException;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
/**
* Support for working with Ant properties and property files.
* @author Jesse Glick
*/
public class PropertyUtils {
private PropertyUtils() {}
/**
* Location in user directory of per-user global properties.
* May be null if netbeans.user is not set.
*/
static final File USER_BUILD_PROPERTIES;
static {
String nbuser = System.getProperty("netbeans.user"); // NOI18N
if (nbuser != null) {
USER_BUILD_PROPERTIES = FileUtil.normalizeFile(new File(nbuser, "build.properties")); // NOI18N
} else {
USER_BUILD_PROPERTIES = null;
}
}
private static Reference/**/ globalPropertyProvider = null;
/**
* Load global properties defined by the IDE in the user directory.
* Currently loads ${netbeans.user}/build.properties if it exists.
*
* Acquires read access.
*
* To listen to changes use {@link #globalPropertyProvider}.
* @return user properties (empty if missing or malformed)
*/
public static EditableProperties getGlobalProperties() {
return (EditableProperties)ProjectManager.mutex().readAccess(new Mutex.Action() {
public Object run() {
if (USER_BUILD_PROPERTIES != null && USER_BUILD_PROPERTIES.isFile() &&
USER_BUILD_PROPERTIES.canRead()) {
try {
InputStream is = new FileInputStream(USER_BUILD_PROPERTIES);
try {
EditableProperties properties = new EditableProperties(true);
properties.load(is);
return properties;
} finally {
is.close();
}
} catch (IOException e) {
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
}
}
// Missing or erroneous.
return new EditableProperties(true);
}
});
}
/**
* Edit global properties defined by the IDE in the user directory.
*
* Acquires write access.
* @param properties user properties to set
* @throws IOException if they could not be stored
* @see #getGlobalProperties
*/
public static void putGlobalProperties(final EditableProperties properties) throws IOException {
try {
ProjectManager.mutex().writeAccess(new Mutex.ExceptionAction() {
public Object run() throws IOException {
if (USER_BUILD_PROPERTIES != null) {
FileObject bp = FileUtil.toFileObject(USER_BUILD_PROPERTIES);
if (bp == null) {
USER_BUILD_PROPERTIES.getParentFile().mkdirs();
new FileOutputStream(USER_BUILD_PROPERTIES).close();
assert USER_BUILD_PROPERTIES.isFile() : "Did not actually make " + USER_BUILD_PROPERTIES;
bp = FileUtil.toFileObject(USER_BUILD_PROPERTIES);
assert bp != null : "Could not make " + USER_BUILD_PROPERTIES + "; no masterfs?";
}
FileLock lock = bp.lock();
try {
OutputStream os = bp.getOutputStream(lock);
try {
properties.store(os);
} finally {
os.close();
}
} finally {
lock.releaseLock();
}
} else {
throw new IOException("Do not know where to store build.properties; must set netbeans.user!"); // NOI18N
}
return null;
}
});
} catch (MutexException e) {
throw (IOException)e.getException();
}
}
/**
* Create a property evaluator based on {@link #getGlobalProperties}
* and {@link #putGlobalProperties}.
* It will supply global properties and fire changes when this file
* is changed.
* @return a property producer
*/
public static synchronized PropertyProvider globalPropertyProvider() {
if (globalPropertyProvider != null) {
PropertyProvider pp = (PropertyProvider)globalPropertyProvider.get();
if (pp != null) {
return pp;
}
}
PropertyProvider gpp;
if (USER_BUILD_PROPERTIES != null) {
gpp = propertiesFilePropertyProvider(USER_BUILD_PROPERTIES);
} else {
gpp = fixedPropertyProvider(Collections.EMPTY_MAP);
}
globalPropertyProvider = new SoftReference(gpp);
return gpp;
}
/**
* Create a property provider based on a properties file.
* The file need not exist at the moment; if it is created or deleted an appropriate
* change will be fired. If its contents are changed on disk a change will also be fired.
* @param propertiesFile a path to a (possibly nonexistent) *.properties file
* @return a supplier of properties from such a file
* @see Properties#load
*/
public static PropertyProvider propertiesFilePropertyProvider(File propertiesFile) {
assert propertiesFile != null;
return new FilePropertyProvider(propertiesFile);
}
/**
* Provider based on a named properties file.
*/
private static final class FilePropertyProvider implements PropertyProvider, FileChangeSupportListener {
private final File properties;
private final List/**/ listeners = new ArrayList();
private Map/**/ cached = null;
private long cachedTime = 0L;
public FilePropertyProvider(File properties) {
this.properties = properties;
FileChangeSupport.DEFAULT.addListener(this, properties);
}
public Map/**/ getProperties() {
long currTime = properties.lastModified();
if (cached == null || cachedTime != currTime) {
cachedTime = currTime;
cached = loadProperties();
}
return cached;
}
private Map/**/ loadProperties() {
// XXX does this need to run in PM.mutex.readAccess?
if (properties.isFile() && properties.canRead()) {
try {
InputStream is = new FileInputStream(properties);
try {
Properties props = new Properties();
props.load(is);
return props;
} finally {
is.close();
}
} catch (IOException e) {
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, e);
}
}
// Missing or erroneous.
return Collections.EMPTY_MAP;
}
private void fireChange() {
cachedTime = -1L; // force reload
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);
}
}
public synchronized void addChangeListener(ChangeListener l) {
listeners.add(l);
}
public synchronized void removeChangeListener(ChangeListener l) {
listeners.remove(l);
}
public void fileCreated(FileChangeSupportEvent event) {
//System.err.println("FPP: " + event);
fireChange();
}
public void fileDeleted(FileChangeSupportEvent event) {
//System.err.println("FPP: " + event);
fireChange();
}
public void fileModified(FileChangeSupportEvent event) {
//System.err.println("FPP: " + event);
fireChange();
}
public String toString() {
return "FilePropertyProvider[" + properties + ":" + getProperties() + "]"; // NOI18N
}
}
/**
* Evaluate all properties in a list of property mappings.
*
* If there are any cyclic definitions within a single mapping,
* the evaluation will fail and return null.
* @param defs an ordered list of property mappings, e.g. {@link EditableProperties} instances
* @param predefs an unevaluated set of initial definitions
* @return values for all defined properties, or null if a circularity error was detected
*/
private static Map/**/ evaluateAll(Map/**/ predefs, List/*
|