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.editor.view.spi;
043:
044: import java.awt.Graphics;
045: import java.awt.Shape;
046: import javax.swing.event.DocumentEvent;
047: import javax.swing.text.AbstractDocument;
048: import javax.swing.text.AttributeSet;
049: import javax.swing.text.BadLocationException;
050: import javax.swing.text.Document;
051: import javax.swing.text.Element;
052: import javax.swing.text.JTextComponent;
053: import javax.swing.text.Position;
054: import javax.swing.text.View;
055: import javax.swing.text.ViewFactory;
056: import org.netbeans.lib.editor.util.PriorityMutex;
057:
058: /**
059: * View that allow to lock the view hierarchy.
060: * It's a filter view that is being installed under the root view.
061: *
062: * @author Miloslav Metelka
063: * @version 1.00
064: */
065:
066: public class LockView extends View {
067:
068: private static final String PROPERTY_VIEW_HIERARCHY_MUTEX = "viewHierarchyMutex"; // NOI18N
069: // Note: FoldHierarchyExecution has the same property
070: private static final String PROPERTY_FOLD_HIERARCHY_MUTEX = "foldHierarchyMutex"; // NOI18N
071:
072: private View view;
073:
074: private PriorityMutex mutex;
075:
076: private AbstractDocument doc;
077:
078: /**
079: * Get mutex used to lock the view hierarchy.
080: * All the services manipulating the view hierarchy
081: * or providing data for the view hierarchy
082: * may choose to lock on this mutex
083: * rather having its own locking mechanism
084: * to simplify their locking model
085: * and eliminate possibility of deadlocks
086: * because of counter-locking.
087: * <br>
088: * The <code>LockView</code> itself uses this mutex
089: * as well as the code folding hierarchy.
090: */
091: public static synchronized PriorityMutex getViewHierarchyMutex(
092: JTextComponent component) {
093: // A single mutex instance must be shared by view and fold hierarchies
094: PriorityMutex mutex = (PriorityMutex) component
095: .getClientProperty(PROPERTY_FOLD_HIERARCHY_MUTEX);
096: if (mutex == null) {
097: mutex = new PriorityMutex();
098: component.putClientProperty(PROPERTY_FOLD_HIERARCHY_MUTEX,
099: mutex);
100: }
101: component.putClientProperty(PROPERTY_VIEW_HIERARCHY_MUTEX,
102: mutex);
103:
104: return mutex;
105: }
106:
107: /**
108: * Find the <code>LockView</code> instance in a view hierarchy
109: * by traversing it up to the root view.
110: *
111: * @param view view in the view hierarchy. <code>null</code> is accepted too.
112: * @return valid instance of <code>LockView</code> or null
113: * if there is no <code>LockView</code> instance present
114: * in the view hierarchy of the view is no longer
115: * part of the view hierarchy.
116: */
117: public static LockView get(View view) {
118: while (view != null && !(view instanceof LockView)) {
119: view = view.getParent();
120: }
121:
122: return (LockView) view;
123: }
124:
125: public LockView(View view) {
126: super (null);
127:
128: this .view = view;
129: // System.out.println("LockView instance created " + System.identityHashCode(this));
130: }
131:
132: public void setParent(View parent) {
133:
134: View origParent = getParent();
135: if (origParent != null && parent != null) {
136: /* This is not truly an errorneous situation
137: * but this does not normally happen
138: * as for complex changes in the text component (e.g. a document replacement)
139: * the whole view hierarchy starting with RootView in TextUI
140: * is being thrown away and recreated.
141: * So this special state is reported
142: * to make sure that this situation will be handled
143: * before this constraint will be removed.
144: */
145: throw new IllegalStateException(
146: "Unexpected state occurred when" // NOI18N
147: + " trying to set non-null parent to LockView with non-null" // NOI18N
148: + " parent already set." // NOI18N
149: );
150: }
151:
152: // Assign the mutex variable if necessary
153: // May be desirable to be synced with getMutex() if necessary
154: if (mutex == null && parent != null) {
155: JTextComponent c = (JTextComponent) parent.getContainer();
156: if (c != null) {
157: mutex = getViewHierarchyMutex(c);
158: }
159: }
160:
161: if (parent != null) {
162: // Check that AbstractDocument is being used.
163: Document maybeAbstractDoc = parent.getDocument();
164: if (!(maybeAbstractDoc instanceof AbstractDocument)) {
165: /**
166: * Although LockView could possibly be changed
167: * to work with just the Document interface
168: * there are bunch of other view implementations
169: * in the editor that expect the AbstractDocument as well
170: * mainly due to the presence of AbstractDocument.readLock()
171: * instead of just the Document.render().
172: * If working with non-AbstractDocument instances
173: * would be a strong requirement the editor's
174: * view implementations would have to be reviewed
175: * before this constraint can be removed.
176: */
177: throw new IllegalStateException(
178: "Currently the LockView" // NOI18N
179: + " is designed to work with AbstractDocument instances only." // NOI18N
180: );
181: }
182:
183: /**
184: * Remember the document for which this LockView and underlying
185: * view hierarchy was created.
186: * If any of the childviews would delegate its getDocument()
187: * to parent and it would end up here then the remembered
188: * document will be returned instead of possibly delegating
189: * to the parent RootView which delegates to component.getDocument().
190: * The component.getDocument() always brings the most fresh
191: * document. However that can be problematic e.g. in the following
192: * case:
193: * <ol>
194: * <li> ViewLayoutQueue executes a task in Layout-Thread
195: * <li> The task properly locks document and then view hierarchy
196: * <li> In AWT thread someone calls JTextComponent.setDocument()
197: * before the task in Layout-Tread finishes
198: * <li> Task calls JTextComponent.getDocument()
199: * and attempts to do doc.readLock().
200: * Normally it should be noop as the document
201: * was already read-locked previously however
202: * here it's a different document so looking from
203: * the new document's perspective the locking order
204: * is exactly opposite than it should be
205: * i.e. first the view hierarchy is locked
206: * and then the (new) document is locked.
207: * This situation may lead to deadlock from counter-locking.
208: * <br>
209: * Remembering of the document here and consistent
210: * use of the view.getDocument() instead of
211: * component.getDocument() in the tasks
212: * executed in the Layout-Thread
213: * should avoid this type of deadlock.
214: * </ol>
215: */
216: this .doc = (AbstractDocument) maybeAbstractDoc;
217: }
218:
219: /* First read-lock the document to prevent deadlocks
220: * from counter-locking.
221: */
222: this .doc.readLock();
223: try {
224: lock();
225: try {
226:
227: setParentLocked(parent);
228:
229: } finally {
230: unlock();
231: }
232:
233: } finally {
234: this .doc.readUnlock();
235: }
236: }
237:
238: protected void setParentLocked(View parent) {
239: // possibly first clear parent in child than in this view
240: // so that getContainer() remains usable
241: if (parent == null && view != null) {
242: view.setParent(null);
243: }
244:
245: super .setParent(parent);
246:
247: // Update child for non-null parent here
248: if (parent != null && view != null) {
249: view.setParent(this );
250: }
251: }
252:
253: /**
254: * Set a new single child of this view.
255: */
256: public void setView(View v) {
257: lock();
258: try {
259:
260: if (view != null) {
261: // get rid of back reference so that the old
262: // hierarchy can be garbage collected.
263: view.setParent(null);
264: }
265: view = v;
266: if (view != null) {
267: view.setParent(this );
268: }
269:
270: } finally {
271: unlock();
272: }
273: }
274:
275: public void lock() {
276: if (mutex != null) {
277: mutex.lock();
278: }
279: }
280:
281: public void unlock() {
282: mutex.unlock(); // should always proceed if a previous lock() succeeded
283: }
284:
285: public boolean isPriorityThreadWaiting() {
286: return mutex.isPriorityThreadWaiting();
287: }
288:
289: /**
290: * Return the thread that holds a lock on the view hierarchy.
291: * <br>
292: * This method is intended for diagnostic purposes only to determine
293: * an intruder thread that entered the view hierarchy without obtaining
294: * the lock first.
295: *
296: * @return thread that currently holds a lock on the hierarchy or null
297: * if there is currently no thread holding a lock on the hierarchy.
298: */
299: public Thread getLockThread() {
300: return mutex.getLockThread();
301: }
302:
303: public void render(Runnable r) {
304: lock();
305: try {
306:
307: r.run();
308:
309: } finally {
310: unlock();
311: }
312: }
313:
314: /**
315: * Fetches the attributes to use when rendering. At this level
316: * there are no attributes. If an attribute is resolved
317: * up the view hierarchy this is the end of the line.
318: */
319: public AttributeSet getAttributes() {
320: return null;
321: }
322:
323: public float getPreferredSpan(int axis) {
324: lock();
325: try {
326:
327: if (view != null) {
328: return view.getPreferredSpan(axis);
329: }
330: return 10;
331:
332: } finally {
333: unlock();
334: }
335: }
336:
337: public float getMinimumSpan(int axis) {
338: lock();
339: try {
340:
341: if (view != null) {
342: return view.getMinimumSpan(axis);
343: }
344: return 10;
345:
346: } finally {
347: unlock();
348: }
349: }
350:
351: public float getMaximumSpan(int axis) {
352: lock();
353: try {
354:
355: if (view != null) {
356: return view.getMaximumSpan(axis);
357: }
358: return Integer.MAX_VALUE;
359:
360: } finally {
361: unlock();
362: }
363: }
364:
365: public void preferenceChanged(View child, boolean width,
366: boolean height) {
367: View parent = getParent();
368: if (parent != null) {
369: parent.preferenceChanged(this , width, height);
370: }
371: }
372:
373: public float getAlignment(int axis) {
374: lock();
375: try {
376:
377: if (view != null) {
378: return view.getAlignment(axis);
379: }
380: return 0;
381:
382: } finally {
383: unlock();
384: }
385: }
386:
387: public void paint(Graphics g, Shape allocation) {
388: lock();
389: try {
390:
391: if (view != null) {
392: view.paint(g, allocation);
393: }
394:
395: } finally {
396: unlock();
397: }
398: }
399:
400: public int getViewCount() {
401: return 1;
402: }
403:
404: /**
405: * Gets the n-th view in this container.
406: *
407: * @param n the number of the view to get
408: * @return the view
409: */
410: public View getView(int n) {
411: return view;
412: }
413:
414: public int getViewIndex(int pos, Position.Bias b) {
415: return 0;
416: }
417:
418: public Shape getChildAllocation(int index, Shape a) {
419: return a;
420: }
421:
422: public Shape modelToView(int pos, Shape a, Position.Bias b)
423: throws BadLocationException {
424: lock();
425: try {
426:
427: if (view != null) {
428: return view.modelToView(pos, a, b);
429: }
430: return null;
431:
432: } finally {
433: unlock();
434: }
435: }
436:
437: public Shape modelToView(int p0, Position.Bias b0, int p1,
438: Position.Bias b1, Shape a) throws BadLocationException {
439: lock();
440: try {
441:
442: if (view != null) {
443: return view.modelToView(p0, b0, p1, b1, a);
444: }
445: return null;
446:
447: } finally {
448: unlock();
449: }
450: }
451:
452: public int viewToModel(float x, float y, Shape a,
453: Position.Bias[] bias) {
454: lock();
455: try {
456:
457: if (view != null) {
458: return view.viewToModel(x, y, a, bias);
459: }
460: return -1;
461:
462: } finally {
463: unlock();
464: }
465: }
466:
467: public int getNextVisualPositionFrom(int pos, Position.Bias b,
468: Shape a, int direction, Position.Bias[] biasRet)
469: throws BadLocationException {
470:
471: lock();
472: try {
473:
474: if (view != null) {
475: return view.getNextVisualPositionFrom(pos, b, a,
476: direction, biasRet);
477: }
478: return -1;
479:
480: } finally {
481: unlock();
482: }
483: }
484:
485: public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
486: lock();
487: try {
488:
489: if (view != null) {
490: view.insertUpdate(e, a, f);
491: }
492:
493: } finally {
494: unlock();
495: }
496: }
497:
498: public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
499: lock();
500: try {
501:
502: if (view != null) {
503: view.removeUpdate(e, a, f);
504: }
505:
506: } finally {
507: unlock();
508: }
509: }
510:
511: public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
512: lock();
513: try {
514:
515: if (view != null) {
516: view.changedUpdate(e, a, f);
517: }
518:
519: } finally {
520: unlock();
521: }
522: }
523:
524: public String getToolTipText(float x, float y, Shape allocation) {
525: lock();
526: try {
527:
528: return (view != null) ? view.getToolTipText(x, y,
529: allocation) : null;
530:
531: } finally {
532: unlock();
533: }
534: }
535:
536: public Document getDocument() {
537: return doc;
538: }
539:
540: public int getStartOffset() {
541: if (view != null) {
542: return view.getStartOffset();
543: }
544: Element elem = getElement();
545: return (elem != null) ? elem.getStartOffset() : 0;
546: }
547:
548: public int getEndOffset() {
549: if (view != null) {
550: return view.getEndOffset();
551: }
552: Element elem = getElement();
553: return (elem != null) ? elem.getEndOffset() : 0;
554: }
555:
556: public Element getElement() {
557: if (view != null) {
558: return view.getElement();
559: }
560: Document doc = getDocument();
561: return (doc != null) ? doc.getDefaultRootElement() : null;
562: }
563:
564: public View breakView(int axis, float len, Shape a) {
565: throw new Error("Can't break lock view"); // NOI18N
566: }
567:
568: public int getResizeWeight(int axis) {
569: lock();
570: try {
571:
572: if (view != null) {
573: return view.getResizeWeight(axis);
574: }
575: return 0;
576:
577: } finally {
578: unlock();
579: }
580: }
581:
582: public void setSize(float width, float height) {
583: lock();
584: try {
585:
586: if (view != null) {
587: view.setSize(width, height);
588: }
589:
590: } finally {
591: unlock();
592: }
593: }
594:
595: }
|