001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.spi.project.support.ant;
043:
044: import java.io.ByteArrayOutputStream;
045: import java.io.File;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.io.OutputStream;
049: import java.util.Collections;
050: import java.util.HashMap;
051: import java.util.Map;
052: import java.util.Properties;
053: import javax.swing.event.ChangeListener;
054: import org.netbeans.api.project.ProjectManager;
055: import org.netbeans.modules.project.ant.FileChangeSupport;
056: import org.netbeans.modules.project.ant.FileChangeSupportEvent;
057: import org.netbeans.modules.project.ant.FileChangeSupportListener;
058: import org.netbeans.modules.project.ant.UserQuestionHandler;
059: import org.openide.ErrorManager;
060: import org.openide.filesystems.FileLock;
061: import org.openide.filesystems.FileObject;
062: import org.openide.filesystems.FileSystem;
063: import org.openide.filesystems.FileUtil;
064: import org.openide.modules.InstalledFileLocator;
065: import org.openide.util.ChangeSupport;
066: import org.openide.util.Mutex;
067: import org.openide.util.NbCollections;
068: import org.openide.util.RequestProcessor;
069: import org.openide.util.UserQuestionException;
070: import org.openide.util.Utilities;
071:
072: /**
073: * Manages the loaded property files for {@link AntProjectHelper}.
074: * @author Jesse Glick
075: */
076: final class ProjectProperties {
077:
078: /** Associated helper. */
079: private final AntProjectHelper helper;
080:
081: /**
082: * Properties loaded from metadata files on disk.
083: * Keys are project-relative paths such as {@link #PROJECT_PROPERTIES_PATH}.
084: * Values are loaded property providers.
085: */
086: private final Map<String, PP> properties = new HashMap<String, PP>();
087:
088: /** @see #getStockPropertyPreprovider */
089: private PropertyProvider stockPropertyPreprovider = null;
090:
091: /** @see #getStandardPropertyEvaluator */
092: private PropertyEvaluator standardPropertyEvaluator = null;
093:
094: /**
095: * Create a project properties helper object.
096: * @param helper the associated helper
097: */
098: public ProjectProperties(AntProjectHelper helper) {
099: this .helper = helper;
100: }
101:
102: /**
103: * Get properties from a given path.
104: * @param path the project-relative path
105: * @return the applicable properties (created if empty; never null)
106: */
107: public EditableProperties getProperties(String path) {
108: EditableProperties ep = getPP(path)
109: .getEditablePropertiesOrNull();
110: if (ep != null) {
111: return ep.cloneProperties();
112: } else {
113: return new EditableProperties(true);
114: }
115: }
116:
117: /**
118: * Store properties in memory.
119: * @param path the project-relative path
120: * @param props the new properties, or null to remove the properties file
121: * @return true if an actual change was made
122: */
123: public boolean putProperties(String path, EditableProperties props) {
124: return getPP(path).put(props);
125: }
126:
127: /**
128: * Write cached properties to disk.
129: * @param the project-relative path
130: * @throws IOException if the file could not be written
131: */
132: public FileLock write(String path) throws IOException {
133: assert properties.containsKey(path);
134: return getPP(path).write();
135: }
136:
137: /**
138: * Make a property provider that loads from this file
139: * and fires changes when it is written to (even in memory).
140: */
141: public PropertyProvider getPropertyProvider(String path) {
142: return getPP(path);
143: }
144:
145: private PP getPP(String path) {
146: PP pp = properties.get(path);
147: if (pp == null) {
148: pp = new PP(path, helper);
149: properties.put(path, pp);
150: }
151: return pp;
152: }
153:
154: private static final class PP implements PropertyProvider,
155: FileChangeSupportListener {
156:
157: private static final RequestProcessor RP = new RequestProcessor(
158: "ProjectProperties.PP.RP"); // NOI18N
159:
160: // XXX lock any loaded property files while the project is modified, to prevent manual editing,
161: // and reload any modified files if the project is unmodified
162:
163: private final String path;
164: private final AntProjectHelper helper;
165: private EditableProperties properties = null;
166: private boolean loaded = false;
167: private final ChangeSupport cs = new ChangeSupport(this );
168: private boolean writing = false;
169:
170: public PP(String path, AntProjectHelper helper) {
171: this .path = path;
172: this .helper = helper;
173: FileChangeSupport.DEFAULT.addListener(this , new File(
174: FileUtil.toFile(dir()), path.replace('/',
175: File.separatorChar)));
176: }
177:
178: private FileObject dir() {
179: return helper.getProjectDirectory();
180: }
181:
182: public EditableProperties getEditablePropertiesOrNull() {
183: if (!loaded) {
184: properties = null;
185: FileObject fo = dir().getFileObject(path);
186: if (fo != null) {
187: try {
188: EditableProperties p;
189: InputStream is = fo.getInputStream();
190: try {
191: p = new EditableProperties(true);
192: p.load(is);
193: } finally {
194: is.close();
195: }
196: properties = p;
197: } catch (IOException e) {
198: ErrorManager.getDefault().notify(
199: ErrorManager.INFORMATIONAL, e);
200: }
201: }
202: loaded = true;
203: }
204: return properties;
205: }
206:
207: public boolean put(EditableProperties nue) {
208: loaded = true;
209: boolean modifying = !Utilities.compareObjects(nue,
210: properties);
211: if (modifying) {
212: if (nue != null) {
213: properties = nue.cloneProperties();
214: } else {
215: properties = null;
216: }
217: fireChange();
218: }
219: return modifying;
220: }
221:
222: public FileLock write() throws IOException {
223: assert loaded;
224: final FileObject f = dir().getFileObject(path);
225: assert !writing;
226: final FileLock[] _lock = new FileLock[1];
227: writing = true;
228: try {
229: if (properties != null) {
230: // Supposed to create/modify the file.
231: // Need to use an atomic action - otherwise listeners will first
232: // receive an event that the file has been written to zero length
233: // (which for *.properties means no keys), which is wrong.
234: dir().getFileSystem().runAtomicAction(
235: new FileSystem.AtomicAction() {
236: public void run() throws IOException {
237: final FileObject _f;
238: if (f == null) {
239: _f = FileUtil.createData(dir(),
240: path);
241: assert _f != null : "FU.cD must not return null; called on "
242: + dir() + " + " + path; // #50802
243: } else {
244: _f = f;
245: }
246: ByteArrayOutputStream baos = new ByteArrayOutputStream();
247: properties.store(baos);
248: final byte[] data = baos
249: .toByteArray();
250: try {
251: _lock[0] = _f.lock(); // released by {@link AntProjectHelper#save}
252: OutputStream os = _f
253: .getOutputStream(_lock[0]);
254: try {
255: os.write(data);
256: } finally {
257: os.close();
258: }
259: } catch (UserQuestionException uqe) { // #46089
260: helper.needPendingHook();
261: UserQuestionHandler
262: .handle(
263: uqe,
264: new UserQuestionHandler.Callback() {
265: public void accepted() {
266: // Try again.
267: assert !writing;
268: writing = true;
269: try {
270: FileLock lock = _f
271: .lock();
272: try {
273: OutputStream os = _f
274: .getOutputStream(lock);
275: try {
276: os
277: .write(data);
278: } finally {
279: os
280: .close();
281: }
282: } finally {
283: lock
284: .releaseLock();
285: }
286: helper
287: .maybeCallPendingHook();
288: } catch (IOException e) {
289: // Oh well.
290: ErrorManager
291: .getDefault()
292: .notify(
293: e);
294: reload();
295: } finally {
296: writing = false;
297: }
298: }
299:
300: public void denied() {
301: reload();
302: }
303:
304: public void error(
305: IOException e) {
306: ErrorManager
307: .getDefault()
308: .notify(
309: e);
310: reload();
311: }
312:
313: private void reload() {
314: helper
315: .cancelPendingHook();
316: // Revert the save.
317: diskChange();
318: }
319: });
320: }
321: }
322: });
323: } else {
324: // We are supposed to remove any existing file.
325: if (f != null) {
326: f.delete();
327: }
328: }
329: } catch (IOException e) {
330: if (_lock[0] != null) {
331: // Release it now, since no one else will.
332: _lock[0].releaseLock();
333: }
334: throw e;
335: } finally {
336: writing = false;
337: }
338: return _lock[0];
339: }
340:
341: public Map<String, String> getProperties() {
342: Map<String, String> props = getEditablePropertiesOrNull();
343: if (props != null) {
344: return Collections.unmodifiableMap(props);
345: } else {
346: return Collections.emptyMap();
347: }
348: }
349:
350: public synchronized void addChangeListener(ChangeListener l) {
351: cs.addChangeListener(l);
352: }
353:
354: public synchronized void removeChangeListener(ChangeListener l) {
355: cs.removeChangeListener(l);
356: }
357:
358: private void fireChange() {
359: if (!cs.hasListeners()) {
360: return;
361: }
362: final Mutex.Action<Void> action = new Mutex.Action<Void>() {
363: public Void run() {
364: cs.fireChange();
365: return null;
366: }
367: };
368: if (ProjectManager.mutex().isWriteAccess()) {
369: // Run it right now. postReadRequest would be too late.
370: ProjectManager.mutex().readAccess(action);
371: } else if (ProjectManager.mutex().isReadAccess()) {
372: // Run immediately also. No need to switch to read access.
373: action.run();
374: } else {
375: // Not safe to acquire a new lock, so run later in read access.
376: RP.post(new Runnable() {
377: public void run() {
378: ProjectManager.mutex().readAccess(action);
379: }
380: });
381: }
382: }
383:
384: private void diskChange() {
385: // XXX should check for a possible clobber from in-memory data
386: if (!writing) {
387: loaded = false;
388: }
389: fireChange();
390: if (!writing) {
391: helper.fireExternalChange(path);
392: }
393: }
394:
395: public void fileCreated(FileChangeSupportEvent event) {
396: diskChange();
397: }
398:
399: public void fileDeleted(FileChangeSupportEvent event) {
400: diskChange();
401: }
402:
403: public void fileModified(FileChangeSupportEvent event) {
404: diskChange();
405: }
406:
407: }
408:
409: /**
410: * See {@link AntProjectHelper#getStockPropertyPreprovider}.
411: */
412: public PropertyProvider getStockPropertyPreprovider() {
413: if (stockPropertyPreprovider == null) {
414: Map<String, String> m;
415: Properties p = System.getProperties();
416: synchronized (p) {
417: m = NbCollections.checkedMapByCopy(p, String.class,
418: String.class, false);
419: }
420: m.put("basedir", FileUtil.toFile(
421: helper.getProjectDirectory()).getAbsolutePath()); // NOI18N
422: File antJar = InstalledFileLocator.getDefault().locate(
423: "ant/lib/ant.jar", "org.apache.tools.ant.module",
424: false); // NOI18N
425: if (antJar != null) {
426: File antHome = antJar.getParentFile().getParentFile();
427: m.put("ant.home", antHome.getAbsolutePath()); // NOI18N
428: m.put("ant.core.lib", antJar.getAbsolutePath()); // NOI18N
429: }
430: stockPropertyPreprovider = PropertyUtils
431: .fixedPropertyProvider(m);
432: }
433: return stockPropertyPreprovider;
434: }
435:
436: /**
437: * See {@link AntProjectHelper#getStandardPropertyEvaluator}.
438: */
439: public PropertyEvaluator getStandardPropertyEvaluator() {
440: if (standardPropertyEvaluator == null) {
441: PropertyEvaluator findUserPropertiesFile = PropertyUtils
442: .sequentialPropertyEvaluator(
443: getStockPropertyPreprovider(),
444: getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH));
445: PropertyProvider globalProperties = PropertyUtils
446: .userPropertiesProvider(findUserPropertiesFile,
447: "user.properties.file", FileUtil
448: .toFile(helper
449: .getProjectDirectory())); // NOI18N
450: standardPropertyEvaluator = PropertyUtils
451: .sequentialPropertyEvaluator(
452: getStockPropertyPreprovider(),
453: getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH),
454: helper
455: .getProjectLibrariesPropertyProvider(),
456: globalProperties,
457: getPropertyProvider(AntProjectHelper.PROJECT_PROPERTIES_PATH));
458: }
459: return standardPropertyEvaluator;
460: }
461:
462: }
|