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 threaddemo.model;
043:
044: import java.io.File;
045: import java.io.FileInputStream;
046: import java.io.FileOutputStream;
047: import java.io.IOException;
048: import java.io.InputStream;
049: import java.io.OutputStream;
050: import java.lang.ref.Reference;
051: import java.lang.ref.WeakReference;
052: import java.util.AbstractList;
053: import java.util.ArrayList;
054: import java.util.Arrays;
055: import java.util.Collections;
056: import java.util.List;
057: import java.util.Map;
058: import java.util.WeakHashMap;
059: import threaddemo.locking.RWLock;
060:
061: /**
062: * A convenience skeleton for making a phadhail based on files.
063: * Supplies the actual file operations, and handles caching and
064: * event firing and so on. Subclasses supply specialized threading
065: * behaviors (this class is not thread-safe, except for adding and
066: * removing listeners).
067: * @author Jesse Glick
068: */
069: public abstract class AbstractPhadhail implements Phadhail {
070:
071: private static final Map<Factory, Map<File, Reference<AbstractPhadhail>>> instances = new WeakHashMap<Factory, Map<File, Reference<AbstractPhadhail>>>();
072:
073: protected interface Factory {
074: AbstractPhadhail create(File f);
075: }
076:
077: private static Map<File, Reference<AbstractPhadhail>> instancesForFactory(
078: Factory y) {
079: assert Thread.holdsLock(AbstractPhadhail.class);
080: Map<File, Reference<AbstractPhadhail>> instances2 = instances
081: .get(y);
082: if (instances2 == null) {
083: instances2 = new WeakHashMap<File, Reference<AbstractPhadhail>>();
084: instances.put(y, instances2);
085: }
086: return instances2;
087: }
088:
089: /** factory */
090: protected static synchronized AbstractPhadhail forFile(File f,
091: Factory y) {
092: Map<File, Reference<AbstractPhadhail>> instances2 = instancesForFactory(y);
093: Reference<AbstractPhadhail> r = instances2.get(f);
094: AbstractPhadhail ph = (r != null) ? r.get() : null;
095: if (ph == null) {
096: // XXX could also yield lock while calling create, but don't bother
097: ph = y.create(f);
098: instances2.put(f, new WeakReference<AbstractPhadhail>(ph));
099: }
100: return ph;
101: }
102:
103: private File f;
104: private List<PhadhailListener> listeners = null;
105: private Reference<List<Phadhail>> kids;
106: private static boolean firing = false;
107:
108: protected AbstractPhadhail(File f) {
109: this .f = f;
110: }
111:
112: /** factory to create new instances of this class; should be a constant */
113: protected abstract Factory factory();
114:
115: public List<Phadhail> getChildren() {
116: assert lock().canRead();
117: List<Phadhail> phs = null;
118: if (kids != null) {
119: phs = kids.get();
120: }
121: if (phs == null) {
122: // Need to (re)calculate the children.
123: File[] fs = f.listFiles();
124: if (fs != null) {
125: Arrays.sort(fs);
126: phs = new ChildrenList(fs);
127: } else {
128: phs = Collections.emptyList();
129: }
130: kids = new WeakReference<List<Phadhail>>(phs);
131: }
132: return phs;
133: }
134:
135: private final class ChildrenList extends AbstractList<Phadhail> {
136: private final File[] files;
137: private final Phadhail[] kids;
138:
139: public ChildrenList(File[] files) {
140: this .files = files;
141: kids = new Phadhail[files.length];
142: }
143:
144: // These methods need not be called with the read lock held
145: // (see Phadhail.getChildren Javadoc).
146: public Phadhail get(int i) {
147: Phadhail ph = kids[i];
148: if (ph == null) {
149: ph = forFile(files[i], factory());
150: }
151: return ph;
152: }
153:
154: public int size() {
155: return files.length;
156: }
157: }
158:
159: public String getName() {
160: assert lock().canRead();
161: return f.getName();
162: }
163:
164: public String getPath() {
165: assert lock().canRead();
166: return f.getAbsolutePath();
167: }
168:
169: public boolean hasChildren() {
170: assert lock().canRead();
171: return f.isDirectory();
172: }
173:
174: /**
175: * add/removePhadhailListener must be called serially
176: */
177: private static final Object LISTENER_LOCK = new String("LP.LL");
178:
179: public final void addPhadhailListener(PhadhailListener l) {
180: synchronized (LISTENER_LOCK) {
181: if (listeners == null) {
182: listeners = new ArrayList<PhadhailListener>();
183: }
184: listeners.add(l);
185: }
186: }
187:
188: public final void removePhadhailListener(PhadhailListener l) {
189: synchronized (LISTENER_LOCK) {
190: if (listeners != null) {
191: listeners.remove(l);
192: if (listeners.isEmpty()) {
193: listeners = null;
194: }
195: }
196: }
197: }
198:
199: private final PhadhailListener[] listeners() {
200: synchronized (LISTENER_LOCK) {
201: if (listeners != null) {
202: return listeners.toArray(new PhadhailListener[listeners
203: .size()]);
204: } else {
205: return null;
206: }
207: }
208: }
209:
210: protected final void fireChildrenChanged() {
211: final PhadhailListener[] l = listeners();
212: if (l != null) {
213: lock().readLater(new Runnable() {
214: public void run() {
215: firing = true;
216: try {
217: PhadhailEvent ev = PhadhailEvent
218: .create(AbstractPhadhail.this );
219: for (PhadhailListener listener : l) {
220: listener.childrenChanged(ev);
221: }
222: } finally {
223: firing = false;
224: }
225: }
226: });
227: }
228: }
229:
230: protected final void fireNameChanged(final String oldName,
231: final String newName) {
232: final PhadhailListener[] l = listeners();
233: if (l != null) {
234: lock().read(new Runnable() {
235: public void run() {
236: firing = true;
237: try {
238: PhadhailNameEvent ev = PhadhailNameEvent
239: .create(AbstractPhadhail.this , oldName,
240: newName);
241: for (PhadhailListener listener : l) {
242: listener.nameChanged(ev);
243: }
244: } finally {
245: firing = false;
246: }
247: }
248: });
249: }
250: }
251:
252: public void rename(String nue) throws IOException {
253: assert lock().canWrite();
254: assert !firing : "Mutation within listener callback";
255: String oldName = getName();
256: if (oldName.equals(nue)) {
257: return;
258: }
259: File newFile = new File(f.getParentFile(), nue);
260: if (!f.renameTo(newFile)) {
261: throw new IOException("Renaming " + f + " to " + nue);
262: }
263: File oldFile = f;
264: f = newFile;
265: synchronized (AbstractPhadhail.class) {
266: Map<File, Reference<AbstractPhadhail>> instances2 = instancesForFactory(factory());
267: instances2.remove(oldFile);
268: instances2.put(newFile,
269: new WeakReference<AbstractPhadhail>(this ));
270: }
271: fireNameChanged(oldName, nue);
272: if (hasChildren()) {
273: // Fire changes in path of children too.
274: List<AbstractPhadhail> recChildren = new ArrayList<AbstractPhadhail>(
275: 100);
276: String prefix = oldFile.getAbsolutePath()
277: + File.separatorChar;
278: synchronized (AbstractPhadhail.class) {
279: for (Reference<AbstractPhadhail> r : instancesForFactory(
280: factory()).values()) {
281: AbstractPhadhail ph = r.get();
282: if (ph != null && ph != this
283: && ph.getPath().startsWith(prefix)) {
284: recChildren.add(ph);
285: }
286: }
287: }
288: // Do the notification after traversing the instances map, since
289: // we cannot mutate the map while an iterator is active.
290: for (AbstractPhadhail ph : recChildren) {
291: ph.parentRenamed(oldFile, newFile);
292: }
293: }
294: }
295:
296: /**
297: * Called when some parent dir has been renamed, and our name
298: * needs to change as well.
299: */
300: private void parentRenamed(File oldParent, File newParent) {
301: String prefix = newParent.getAbsolutePath();
302: String suffix = f.getAbsolutePath().substring(
303: oldParent.getAbsolutePath().length());
304: File oldFile = f;
305: f = new File(prefix + suffix);
306: synchronized (AbstractPhadhail.class) {
307: Map<File, Reference<AbstractPhadhail>> instances2 = instancesForFactory(factory());
308: instances2.remove(oldFile);
309: instances2
310: .put(f, new WeakReference<AbstractPhadhail>(this ));
311: }
312: fireNameChanged(null, null);
313: }
314:
315: public Phadhail createContainerPhadhail(String name)
316: throws IOException {
317: assert lock().canWrite();
318: assert !firing : "Mutation within listener callback";
319: File child = new File(f, name);
320: if (!child.mkdir()) {
321: throw new IOException("Creating dir " + child);
322: }
323: fireChildrenChanged();
324: return forFile(child, factory());
325: }
326:
327: public Phadhail createLeafPhadhail(String name) throws IOException {
328: assert lock().canWrite();
329: assert !firing : "Mutation within listener callback";
330: File child = new File(f, name);
331: if (!child.createNewFile()) {
332: throw new IOException("Creating file " + child);
333: }
334: fireChildrenChanged();
335: return forFile(child, factory());
336: }
337:
338: public void delete() throws IOException {
339: assert lock().canWrite();
340: assert !firing : "Mutation within listener callback";
341: if (!f.delete()) {
342: throw new IOException("Deleting file " + f);
343: }
344: forFile(f.getParentFile(), factory()).fireChildrenChanged();
345: }
346:
347: public InputStream getInputStream() throws IOException {
348: assert lock().canRead();
349: return new FileInputStream(f);
350: }
351:
352: public OutputStream getOutputStream() throws IOException {
353: // Yes, read access - for the sake of the demo, currently Phadhail.getOutputStream
354: // is not considered a mutator method (fires no changes); this would be different
355: // if PhadhailListener included a content change event.
356: // That would be trickier because then you would need to acquire the write lock
357: // when opening the stream but release it when closing the stream (*not* when
358: // returning it to the caller).
359: assert lock().canRead();
360: return new FileOutputStream(f);
361: }
362:
363: public String toString() {
364: String clazz = getClass().getName();
365: int i = clazz.lastIndexOf('.');
366: return clazz.substring(i + 1) + "<" + f + ">";
367: }
368:
369: public abstract RWLock lock();
370:
371: }
|