0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.modules.viewmodel;
0043:
0044: import java.awt.datatransfer.Transferable;
0045: import java.awt.event.ActionEvent;
0046: import java.awt.event.KeyEvent;
0047: import java.beans.PropertyEditor;
0048: import java.lang.IllegalAccessException;
0049: import java.lang.ref.WeakReference;
0050: import java.io.IOException;
0051: import java.util.ArrayList;
0052: import java.util.Arrays;
0053: import java.util.Collections;
0054: import java.util.HashMap;
0055: import java.util.Iterator;
0056: import java.util.LinkedList;
0057: import java.util.List;
0058: import java.util.Map;
0059: import java.util.WeakHashMap;
0060: import javax.swing.AbstractAction;
0061: import javax.swing.Action;
0062: import javax.swing.KeyStroke;
0063: import javax.swing.SwingUtilities;
0064:
0065: import org.netbeans.spi.viewmodel.ColumnModel;
0066: import org.netbeans.spi.viewmodel.ModelEvent;
0067: import org.netbeans.spi.viewmodel.Models;
0068: import org.netbeans.spi.viewmodel.TreeModel;
0069: import org.netbeans.spi.viewmodel.UnknownTypeException;
0070: import org.openide.ErrorManager;
0071:
0072: import org.openide.nodes.AbstractNode;
0073: import org.openide.nodes.Children;
0074: import org.openide.nodes.Node;
0075: import org.openide.nodes.PropertySupport;
0076: import org.openide.nodes.Sheet;
0077: import org.openide.util.NbBundle;
0078: import org.openide.util.RequestProcessor;
0079: import org.openide.util.RequestProcessor.Task;
0080: import org.openide.util.datatransfer.PasteType;
0081: import org.openide.util.lookup.Lookups;
0082:
0083: /**
0084: *
0085: * @author Jan Jancura
0086: */
0087: public class TreeModelNode extends AbstractNode {
0088:
0089: /**
0090: * The maximum length of text that is interpreted as HTML.
0091: * This is documented at openide/explorer/src/org/openide/explorer/doc-files/propertyViewCustomization.html
0092: */
0093: private static final int MAX_HTML_LENGTH = 511;
0094:
0095: // variables ...............................................................
0096:
0097: private Models.CompoundModel model;
0098: private TreeModelRoot treeModelRoot;
0099: private Object object;
0100:
0101: private String htmlDisplayName;
0102: private Map<String, Object> properties = new HashMap<String, Object>();
0103:
0104: // init ....................................................................
0105:
0106: /**
0107: * Creates root of call stack for given producer.
0108: */
0109: public TreeModelNode(final Models.CompoundModel model,
0110: final TreeModelRoot treeModelRoot, final Object object) {
0111: super (createChildren(model, treeModelRoot, object), Lookups
0112: .singleton(object));
0113: this .model = model;
0114: this .treeModelRoot = treeModelRoot;
0115: this .object = object;
0116:
0117: // <RAVE>
0118: // Use the modified CompoundModel class's field to set the
0119: // propertiesHelpID for properties sheets if the model's helpID
0120: // has been set
0121: if (model.getHelpId() != null) {
0122: this .setValue("propertiesHelpID", model.getHelpId()); // NOI18N
0123: }
0124: // </RAVE>
0125:
0126: treeModelRoot.registerNode(object, this );
0127: refreshNode();
0128: initProperties();
0129: }
0130:
0131: // Node implementation .....................................................
0132:
0133: private void initProperties() {
0134: Sheet sheet = Sheet.createDefault();
0135: Sheet.Set ps = Sheet.createPropertiesSet();
0136: ColumnModel[] columns = model.getColumns();
0137: int i, k = columns.length;
0138: for (i = 0; i < k; i++)
0139: ps.put(new MyProperty(columns[i], treeModelRoot));
0140: sheet.put(ps);
0141: setSheet(sheet);
0142: }
0143:
0144: private static Children createChildren(Models.CompoundModel model,
0145: TreeModelRoot treeModelRoot, Object object) {
0146: if (object == null)
0147: throw new NullPointerException();
0148: try {
0149: return model.isLeaf(object) ? Children.LEAF
0150: : new TreeModelChildren(model, treeModelRoot,
0151: object);
0152: } catch (UnknownTypeException e) {
0153: if (!(object instanceof String)) {
0154: Throwable t = ErrorManager.getDefault().annotate(e,
0155: "Model: " + model);
0156: ErrorManager.getDefault().notify(
0157: ErrorManager.INFORMATIONAL, t);
0158: }
0159: return Children.LEAF;
0160: }
0161: }
0162:
0163: public String getShortDescription() {
0164: try {
0165: String shortDescription = model.getShortDescription(object);
0166: if (shortDescription != null) {
0167: shortDescription = adjustHTML(shortDescription);
0168: }
0169: return shortDescription;
0170: } catch (UnknownTypeException e) {
0171: if (!(object instanceof String)) {
0172: Throwable t = ErrorManager.getDefault().annotate(e,
0173: "Model: " + model);
0174: ErrorManager.getDefault().notify(
0175: ErrorManager.INFORMATIONAL, t);
0176: }
0177: return null;
0178: }
0179: }
0180:
0181: public String getHtmlDisplayName() {
0182: return htmlDisplayName;
0183: }
0184:
0185: public Action[] getActions(boolean context) {
0186: if (context)
0187: return treeModelRoot.getRootNode().getActions(false);
0188: try {
0189: return model.getActions(object);
0190: } catch (UnknownTypeException e) {
0191: // NodeActionsProvider is voluntary
0192: return new Action[0];
0193: }
0194: }
0195:
0196: public Action getPreferredAction() {
0197: return new AbstractAction() {
0198: public void actionPerformed(ActionEvent e) {
0199: try {
0200: model.performDefaultAction(object);
0201: } catch (UnknownTypeException ex) {
0202: // NodeActionsProvider is voluntary
0203: }
0204: }
0205: };
0206: }
0207:
0208: public boolean canDestroy() {
0209: try {
0210: Action[] as = model.getActions(object);
0211: int i, k = as.length;
0212: for (i = 0; i < k; i++) {
0213: if (as[i] == null)
0214: continue;
0215: Object key = as[i].getValue(Action.ACCELERATOR_KEY);
0216: if ((key != null)
0217: && (key
0218: .equals(KeyStroke
0219: .getKeyStroke("DELETE"))))
0220: return as[i].isEnabled();
0221: }
0222: return false;
0223: } catch (UnknownTypeException e) {
0224: // NodeActionsProvider is voluntary
0225: return false;
0226: }
0227: }
0228:
0229: public boolean canCopy() {
0230: try {
0231: return model.canCopy(object);
0232: } catch (UnknownTypeException e) {
0233: Throwable t = ErrorManager.getDefault().annotate(e,
0234: "Model: " + model);
0235: ErrorManager.getDefault().notify(
0236: ErrorManager.INFORMATIONAL, t);
0237: return false;
0238: }
0239: }
0240:
0241: public boolean canCut() {
0242: try {
0243: return model.canCut(object);
0244: } catch (UnknownTypeException e) {
0245: Throwable t = ErrorManager.getDefault().annotate(e,
0246: "Model: " + model);
0247: ErrorManager.getDefault().notify(
0248: ErrorManager.INFORMATIONAL, t);
0249: return false;
0250: }
0251: }
0252:
0253: public void destroy() {
0254: try {
0255: Action[] as = model.getActions(object);
0256: int i, k = as.length;
0257: for (i = 0; i < k; i++) {
0258: if (as[i] == null)
0259: continue;
0260: Object key = as[i].getValue(Action.ACCELERATOR_KEY);
0261: if ((key != null)
0262: && (key
0263: .equals(KeyStroke
0264: .getKeyStroke("DELETE")))) {
0265: as[i].actionPerformed(null);
0266: return;
0267: }
0268: }
0269: } catch (UnknownTypeException e) {
0270: Throwable t = ErrorManager.getDefault().annotate(e,
0271: "Model: " + model);
0272: ErrorManager.getDefault().notify(
0273: ErrorManager.INFORMATIONAL, t);
0274: }
0275: }
0276:
0277: // other methods ...........................................................
0278:
0279: void setObject(Object o) {
0280: setObjectNoRefresh(o);
0281: refresh();
0282: }
0283:
0284: private void setObjectNoRefresh(Object o) {
0285: object = o;
0286: Children ch = getChildren();
0287: if (ch instanceof TreeModelChildren)
0288: ((TreeModelChildren) ch).object = o;
0289: }
0290:
0291: public Object getObject() {
0292: return object;
0293: }
0294:
0295: private Task task;
0296:
0297: void refresh() {
0298: // 1) empty cache
0299: synchronized (properties) {
0300: properties.clear();
0301: }
0302:
0303: // 2) refresh name, displayName and iconBase
0304: if (task == null) {
0305: task = getRequestProcessor().create(new Runnable() {
0306: public void run() {
0307: refreshNode();
0308: fireShortDescriptionChange(null, null);
0309:
0310: // 3) refresh children
0311: refreshTheChildren(true);
0312: }
0313: });
0314: }
0315: task.schedule(0);
0316: }
0317:
0318: void refresh(int changeMask) {
0319: if (changeMask == 0xFFFFFFFF) {
0320: refresh();
0321: return;
0322: }
0323: if ((ModelEvent.NodeChanged.DISPLAY_NAME_MASK & changeMask) != 0) {
0324: try {
0325: String name = model.getDisplayName(object);
0326: if (name == null) {
0327: Throwable t = new NullPointerException("Model: "
0328: + model + ".getDisplayName (" + object
0329: + ") = null!");
0330: ErrorManager.getDefault().notify(t);
0331: }
0332: setName(name, false);
0333: } catch (UnknownTypeException e) {
0334: Throwable t = ErrorManager.getDefault().annotate(e,
0335: "Model: " + model);
0336: ErrorManager.getDefault().notify(
0337: ErrorManager.INFORMATIONAL, t);
0338: }
0339: } else if ((ModelEvent.NodeChanged.ICON_MASK & changeMask) != 0) {
0340: try {
0341: String iconBase = model
0342: .getIconBaseWithExtension(object);
0343: if (iconBase != null)
0344: setIconBaseWithExtension(iconBase);
0345: else
0346: setIconBaseWithExtension("org/openide/resources/actions/empty.gif");
0347: } catch (UnknownTypeException e) {
0348: Throwable t = ErrorManager.getDefault().annotate(e,
0349: "Model: " + model);
0350: ErrorManager.getDefault().notify(
0351: ErrorManager.INFORMATIONAL, t);
0352: }
0353: } else if ((ModelEvent.NodeChanged.SHORT_DESCRIPTION_MASK & changeMask) != 0) {
0354: fireShortDescriptionChange(null, null);
0355: } else if ((ModelEvent.NodeChanged.CHILDREN_MASK & changeMask) != 0) {
0356: getRequestProcessor().post(new Runnable() {
0357: public void run() {
0358: refreshTheChildren(false);
0359: }
0360: });
0361: } else {
0362: refresh();
0363: }
0364: }
0365:
0366: private static RequestProcessor requestProcessor;
0367:
0368: static RequestProcessor getRequestProcessor() {
0369: if (requestProcessor == null)
0370: requestProcessor = new RequestProcessor("TreeModel", 1);
0371: return requestProcessor;
0372: }
0373:
0374: private void setName(String name, boolean italics) {
0375: // XXX HACK: HTMLDisplayName is missing in the models!
0376: String oldHtmlDisplayName = htmlDisplayName;
0377: String oldDisplayName = getDisplayName();
0378:
0379: String newDisplayName;
0380: if (name.startsWith("<html>")) {
0381: htmlDisplayName = name;
0382: newDisplayName = removeHTML(name);
0383: } else {
0384: htmlDisplayName = null;
0385: newDisplayName = name;
0386: }
0387: if ((oldDisplayName == null)
0388: || !oldDisplayName.equals(newDisplayName)) {
0389: setDisplayName(newDisplayName);
0390: } else {
0391: if (oldHtmlDisplayName != null
0392: && !oldHtmlDisplayName.equals(htmlDisplayName)
0393: || htmlDisplayName != null
0394: && !htmlDisplayName.equals(oldHtmlDisplayName)) {
0395:
0396: // Display names are equal, but HTML display names differ!
0397: // We hope that this is sufficient to refresh the HTML display name:
0398: fireDisplayNameChange(oldDisplayName + "_HACK",
0399: getDisplayName());
0400: }
0401: }
0402: }
0403:
0404: private void refreshNode() {
0405: try {
0406: String name = model.getDisplayName(object);
0407: if (name == null) {
0408: Throwable t = new NullPointerException("Model: "
0409: + model + ".getDisplayName (" + object
0410: + ") = null!");
0411: ErrorManager.getDefault().notify(t);
0412: }
0413: setName(name, false);
0414: String iconBase = null;
0415: if (model.getRoot() != object) {
0416: iconBase = model.getIconBaseWithExtension(object);
0417: }
0418: if (iconBase != null)
0419: setIconBaseWithExtension(iconBase);
0420: else
0421: setIconBaseWithExtension("org/openide/resources/actions/empty.gif");
0422: firePropertyChange(null, null, null);
0423: } catch (UnknownTypeException e) {
0424: Throwable t = ErrorManager.getDefault().annotate(e,
0425: "Model: " + model);
0426: ErrorManager.getDefault().notify(
0427: ErrorManager.INFORMATIONAL, t);
0428: }
0429: }
0430:
0431: void refreshColumn(String column) {
0432: synchronized (properties) {
0433: properties.remove(column);
0434: properties.remove(column + "#html");
0435: }
0436: firePropertyChange(column, null, null);
0437: }
0438:
0439: private void refreshTheChildren(boolean refreshSubNodes) {
0440: Children ch = getChildren();
0441: try {
0442: if (ch instanceof TreeModelChildren) {
0443: if (model.isLeaf(object)) {
0444: setChildren(Children.LEAF);
0445: } else {
0446: ((TreeModelChildren) ch)
0447: .refreshChildren(refreshSubNodes);
0448: }
0449: } else if (!model.isLeaf(object)) {
0450: setChildren(new TreeModelChildren(model, treeModelRoot,
0451: object));
0452: }
0453: } catch (UnknownTypeException utex) {
0454: // not known - do not change children
0455: if (!(object instanceof String)) {
0456: Throwable t = ErrorManager.getDefault().annotate(utex,
0457: "Model: " + model);
0458: ErrorManager.getDefault().notify(
0459: ErrorManager.INFORMATIONAL, t);
0460: }
0461: setChildren(Children.LEAF);
0462: }
0463: }
0464:
0465: private static String htmlValue(String name) {
0466: if (!(name.length() > 6 && name.substring(0, 6)
0467: .equalsIgnoreCase("<html>")))
0468: return null;
0469: if (name.length() > MAX_HTML_LENGTH) {
0470: int endTagsPos = findEndTagsPos(name);
0471: String ending = name.substring(endTagsPos + 1);
0472: name = name.substring(0, MAX_HTML_LENGTH - 3
0473: - ending.length());
0474: // Check whether we haven't cut "&...;" in between:
0475: int n = name.length();
0476: for (int i = n - 1; i > n - 6; i--) {
0477: if (name.charAt(i) == ';') {
0478: break; // We have an end of the group
0479: }
0480: if (name.charAt(i) == '&') {
0481: name = name.substring(0, i);
0482: break;
0483: }
0484: }
0485: name += "..." + ending;
0486: }
0487: return adjustHTML(name);
0488: }
0489:
0490: private static int findEndTagsPos(String s) {
0491: int openings = 0;
0492: int i;
0493: for (i = s.length() - 1; i >= 0; i--) {
0494: if (s.charAt(i) == '>')
0495: openings++;
0496: else if (s.charAt(i) == '<')
0497: openings--;
0498: else if (openings == 0)
0499: break;
0500: }
0501: return i;
0502: }
0503:
0504: private static String removeHTML(String text) {
0505: if (!(text.length() > 6 && text.substring(0, 6)
0506: .equalsIgnoreCase("<html>"))) {
0507: return text;
0508: }
0509: text = text.replaceAll("<i>", "");
0510: text = text.replaceAll("</i>", "");
0511: text = text.replaceAll("<b>", "");
0512: text = text.replaceAll("</b>", "");
0513: text = text.replaceAll("<html>", "");
0514: text = text.replaceAll("</html>", "");
0515: text = text.replaceAll("</font>", "");
0516: int i = text.indexOf("<font");
0517: while (i >= 0) {
0518: int j = text.indexOf(">", i);
0519: text = text.substring(0, i) + text.substring(j + 1);
0520: i = text.indexOf("<font");
0521: }
0522: text = text.replaceAll("<", "<");
0523: text = text.replaceAll(">", ">");
0524: text = text.replaceAll("&", "&");
0525: return text;
0526: }
0527:
0528: /** Adjusts HTML text so that it's rendered correctly.
0529: * In particular, this assures that white characters are visible.
0530: */
0531: private static String adjustHTML(String text) {
0532: text = text.replaceAll(java.util.regex.Matcher
0533: .quoteReplacement("\\"), "\\\\\\\\");
0534: StringBuffer sb = null;
0535: int j = 0;
0536: for (int i = 0; i < text.length(); i++) {
0537: char c = text.charAt(i);
0538: String replacement = null;
0539: if (c == '\n') {
0540: replacement = "\\n";
0541: } else if (c == '\r') {
0542: replacement = "\\r";
0543: } else if (c == '\f') {
0544: replacement = "\\f";
0545: } else if (c == '\b') {
0546: replacement = "\\b";
0547: }
0548: if (replacement != null) {
0549: if (sb == null) {
0550: sb = new StringBuffer(text.substring(0, i));
0551: } else {
0552: sb.append(text.substring(j, i));
0553: }
0554: sb.append(replacement);
0555: j = i + 1;
0556: }
0557: }
0558: if (sb == null) {
0559: return text;
0560: } else {
0561: sb.append(text.substring(j));
0562: return sb.toString();
0563: }
0564: }
0565:
0566: public boolean canRename() {
0567: try {
0568: return model.canRename(object);
0569: } catch (UnknownTypeException e) {
0570: Throwable t = ErrorManager.getDefault().annotate(e,
0571: "Model: " + model);
0572: ErrorManager.getDefault().notify(
0573: ErrorManager.INFORMATIONAL, t);
0574: return false;
0575: }
0576: }
0577:
0578: public void setName(String s) {
0579: try {
0580: model.setName(object, s);
0581: super .setName(s);
0582: } catch (UnknownTypeException e) {
0583: Throwable t = ErrorManager.getDefault().annotate(e,
0584: "Model: " + model);
0585: ErrorManager.getDefault().notify(
0586: ErrorManager.INFORMATIONAL, t);
0587: }
0588: }
0589:
0590: public Transferable clipboardCopy() throws IOException {
0591: Transferable t;
0592: try {
0593: t = model.clipboardCopy(object);
0594: } catch (UnknownTypeException e) {
0595: Throwable th = ErrorManager.getDefault().annotate(e,
0596: "Model: " + model);
0597: ErrorManager.getDefault().notify(
0598: ErrorManager.INFORMATIONAL, th);
0599: t = null;
0600: }
0601: if (t == null) {
0602: return super .clipboardCopy();
0603: } else {
0604: return t;
0605: }
0606: }
0607:
0608: public Transferable clipboardCut() throws IOException {
0609: Transferable t;
0610: try {
0611: t = model.clipboardCut(object);
0612: } catch (UnknownTypeException e) {
0613: Throwable th = ErrorManager.getDefault().annotate(e,
0614: "Model: " + model);
0615: ErrorManager.getDefault().notify(
0616: ErrorManager.INFORMATIONAL, th);
0617: t = null;
0618: }
0619: if (t == null) {
0620: return super .clipboardCut();
0621: } else {
0622: return t;
0623: }
0624: }
0625:
0626: /*
0627: public Transferable drag() throws IOException {
0628: Transferable t;
0629: try {
0630: t = model.drag(object);
0631: } catch (UnknownTypeException e) {
0632: Throwable th = ErrorManager.getDefault().annotate(e, "Model: "+model);
0633: ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, th);
0634: t = null;
0635: }
0636: if (t == null) {
0637: return super.drag();
0638: } else {
0639: return t;
0640: }
0641: }
0642: */
0643:
0644: public void createPasteTypes(Transferable t, List<PasteType> l) {
0645: PasteType[] p;
0646: try {
0647: p = model.getPasteTypes(object, t);
0648: } catch (UnknownTypeException e) {
0649: Throwable th = ErrorManager.getDefault().annotate(e,
0650: "Model: " + model);
0651: ErrorManager.getDefault().notify(
0652: ErrorManager.INFORMATIONAL, th);
0653: p = null;
0654: }
0655: if (p == null) {
0656: super .createPasteTypes(t, l);
0657: } else {
0658: l.addAll(Arrays.asList(p));
0659: }
0660: }
0661:
0662: /*
0663: public PasteType getDropType(Transferable t, int action, int index) {
0664: PasteType p;
0665: try {
0666: p = model.getDropType(object, t, action, index);
0667: } catch (UnknownTypeException e) {
0668: Throwable th = ErrorManager.getDefault().annotate(e, "Model: "+model);
0669: ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, th);
0670: p = null;
0671: }
0672: if (p == null) {
0673: return super.getDropType(t, action, index);
0674: } else {
0675: return p;
0676: }
0677: }
0678: */
0679:
0680: // innerclasses ............................................................
0681: /** Special locals subnodes (children) */
0682: private static final class TreeModelChildren extends
0683: Children.Keys<Object> implements LazyEvaluator.Evaluable {
0684:
0685: private boolean initialezed = false;
0686: private Models.CompoundModel model;
0687: private TreeModelRoot treeModelRoot;
0688: private Object object;
0689: private WeakHashMap<Object, WeakReference<TreeModelNode>> objectToNode = new WeakHashMap<Object, WeakReference<TreeModelNode>>();
0690: private int[] evaluated = { 0 }; // 0 - not yet, 1 - evaluated, -1 - timeouted
0691: private Object[] children_evaluated;
0692: private boolean refreshingSubNodes = true;
0693: private boolean refreshingStarted = true;
0694:
0695: private static final Object WAIT_KEY = new Object();
0696:
0697: TreeModelChildren(Models.CompoundModel model,
0698: TreeModelRoot treeModelRoot, Object object) {
0699: this .model = model;
0700: this .treeModelRoot = treeModelRoot;
0701: this .object = object;
0702: }
0703:
0704: protected void addNotify() {
0705: initialezed = true;
0706: refreshChildren(true);
0707: }
0708:
0709: protected void removeNotify() {
0710: initialezed = false;
0711: setKeys(Collections.emptySet());
0712: }
0713:
0714: void refreshChildren(boolean refreshSubNodes) {
0715: if (!initialezed)
0716: return;
0717:
0718: refreshLazyChildren(refreshSubNodes);
0719: }
0720:
0721: public void evaluateLazily(Runnable evaluatedNotify) {
0722: synchronized (evaluated) {
0723: refreshingStarted = false;
0724: }
0725: Object[] ch;
0726: try {
0727: int count = model.getChildrenCount(object);
0728: ch = model.getChildren(object, 0, count);
0729: } catch (UnknownTypeException e) {
0730: ch = new Object[0];
0731: if (!(object instanceof String)) {
0732: Throwable t = ErrorManager.getDefault().annotate(e,
0733: "Model: " + model);
0734: ErrorManager.getDefault().notify(
0735: ErrorManager.INFORMATIONAL, t);
0736: }
0737: } catch (ThreadDeath td) {
0738: throw td;
0739: } catch (Throwable t) {
0740: // recover from defect in getChildren()
0741: // Otherwise there would remain "Please wait..." node.
0742: ErrorManager.getDefault().notify(t);
0743: ch = new Object[0];
0744: }
0745: evaluatedNotify.run();
0746: boolean fire;
0747: synchronized (evaluated) {
0748: int eval = evaluated[0];
0749: if (refreshingStarted) {
0750: fire = false;
0751: } else {
0752: fire = evaluated[0] == -1;
0753: if (!fire) {
0754: children_evaluated = ch;
0755: }
0756: evaluated[0] = 1;
0757: evaluated.notifyAll();
0758: }
0759: //System.err.println(this.hashCode()+" evaluateLazily() ready, evaluated[0] = "+eval+" => fire = "+fire+", refreshingStarted = "+refreshingStarted+", children_evaluated = "+(children_evaluated != null));
0760: }
0761: if (fire) {
0762: applyChildren(ch, refreshingSubNodes);
0763: }
0764: }
0765:
0766: private void refreshLazyChildren(boolean refreshSubNodes) {
0767: synchronized (evaluated) {
0768: evaluated[0] = 0;
0769: refreshingStarted = true;
0770: this .refreshingSubNodes = refreshSubNodes;
0771: //System.err.println(this.hashCode()+" refreshLazyChildren() started = true, evaluated = 0");
0772: }
0773: // It's refresh => do not check for this children already being evaluated
0774: treeModelRoot.getChildrenEvaluator().evaluate(this , false);
0775: Object[] ch;
0776: synchronized (evaluated) {
0777: if (evaluated[0] != 1) {
0778: try {
0779: evaluated.wait(200);
0780: } catch (InterruptedException iex) {
0781: }
0782: if (evaluated[0] != 1) {
0783: evaluated[0] = -1; // timeout
0784: ch = null;
0785: } else {
0786: ch = children_evaluated;
0787: }
0788: } else {
0789: ch = children_evaluated;
0790: }
0791: //System.err.println(this.hashCode()+" refreshLazyChildren() ending, evaluated[0] = "+evaluated[0]+", refreshingStarted = "+refreshingStarted+", children_evaluated = "+(children_evaluated != null)+", ch = "+(ch != null));
0792: // Do nothing when it's evaluated, but already unset.
0793: if (children_evaluated == null && evaluated[0] == 1)
0794: return;
0795: children_evaluated = null;
0796: }
0797: if (ch == null) {
0798: applyWaitChildren();
0799: } else {
0800: applyChildren(ch, refreshSubNodes);
0801: }
0802: }
0803:
0804: private void applyChildren(final Object[] ch,
0805: boolean refreshSubNodes) {
0806: //System.err.println(this.hashCode()+" applyChildren("+refreshSubNodes+")");
0807: int i, k = ch.length;
0808: WeakHashMap<Object, WeakReference<TreeModelNode>> newObjectToNode = new WeakHashMap<Object, WeakReference<TreeModelNode>>();
0809: for (i = 0; i < k; i++) {
0810: if (ch[i] == null) {
0811: throw (NullPointerException) ErrorManager
0812: .getDefault().annotate(
0813: new NullPointerException(),
0814: "model: " + model + "\nparent: "
0815: + object);
0816: }
0817: WeakReference<TreeModelNode> wr = objectToNode
0818: .get(ch[i]);
0819: if (wr == null)
0820: continue;
0821: TreeModelNode tmn = wr.get();
0822: if (tmn == null)
0823: continue;
0824: if (refreshSubNodes) {
0825: tmn.setObject(ch[i]);
0826: } else {
0827: tmn.setObjectNoRefresh(ch[i]);
0828: }
0829: newObjectToNode.put(ch[i], wr);
0830: }
0831: objectToNode = newObjectToNode;
0832: setKeys(ch);
0833:
0834: SwingUtilities.invokeLater(new Runnable() {
0835: public void run() {
0836: int i, k = ch.length;
0837: for (i = 0; i < k; i++)
0838: try {
0839: if (model.isExpanded(ch[i])) {
0840: TreeTable treeTable = treeModelRoot
0841: .getTreeTable();
0842: if (treeTable.isExpanded(object)) {
0843: // Expand the child only if the parent is expanded
0844: treeTable.expandNode(ch[i]);
0845: }
0846: }
0847: } catch (UnknownTypeException ex) {
0848: }
0849: }
0850: });
0851: }
0852:
0853: private void applyWaitChildren() {
0854: //System.err.println(this.hashCode()+" applyWaitChildren()");
0855: setKeys(new Object[] { WAIT_KEY });
0856: }
0857:
0858: // protected void destroyNodes (Node[] nodes) {
0859: // int i, k = nodes.length;
0860: // for (i = 0; i < k; i++) {
0861: // TreeModelNode tmn = (TreeModelNode) nodes [i];
0862: // String name = null;
0863: // try {
0864: // name = model.getDisplayName (tmn.object);
0865: // } catch (UnknownTypeException e) {
0866: // }
0867: // if (name != null)
0868: // nameToChild.remove (name);
0869: // }
0870: // }
0871:
0872: protected Node[] createNodes(Object object) {
0873: if (object == WAIT_KEY) {
0874: AbstractNode n = new AbstractNode(Children.LEAF);
0875: n.setName(NbBundle.getMessage(TreeModelNode.class,
0876: "WaitNode"));
0877: n
0878: .setIconBaseWithExtension("org/netbeans/modules/viewmodel/wait.gif");
0879: return new Node[] { n };
0880: }
0881: if (object instanceof Exception)
0882: return new Node[] { new ExceptionNode(
0883: (Exception) object) };
0884: TreeModelNode tmn = new TreeModelNode(model, treeModelRoot,
0885: object);
0886: objectToNode.put(object, new WeakReference<TreeModelNode>(
0887: tmn));
0888: return new Node[] { tmn };
0889: }
0890: } // ItemChildren
0891:
0892: private class MyProperty extends PropertySupport implements
0893: LazyEvaluator.Evaluable {
0894:
0895: private final String EVALUATING_STR = NbBundle.getMessage(
0896: TreeModelNode.class, "EvaluatingProp");
0897: private String id;
0898: private ColumnModel columnModel;
0899: private boolean nodeColumn;
0900: private TreeModelRoot treeModelRoot;
0901: private int[] evaluated = { 0 }; // 0 - not yet, 1 - evaluated, -1 - timeouted
0902:
0903: MyProperty(ColumnModel columnModel, TreeModelRoot treeModelRoot) {
0904: super (columnModel.getID(),
0905: (columnModel.getType() == null) ? String.class
0906: : columnModel.getType(), columnModel
0907: .getDisplayName(), columnModel
0908: .getShortDescription(), true, true);
0909: this .columnModel = columnModel;
0910: this .nodeColumn = columnModel.getType() == null;
0911: this .treeModelRoot = treeModelRoot;
0912: id = columnModel.getID();
0913: }
0914:
0915: /* Can write the value of the property.
0916: * Returns the value passed into constructor.
0917: * @return <CODE>true</CODE> if the read of the value is supported
0918: */
0919: public boolean canWrite() {
0920: if (nodeColumn)
0921: return false;
0922: try {
0923: return !model.isReadOnly(object, columnModel.getID());
0924: } catch (UnknownTypeException e) {
0925: if (!(object instanceof String)) {
0926: Throwable t = ErrorManager.getDefault().annotate(
0927: e,
0928: "Column id:" + columnModel.getID()
0929: + "\nModel: " + model);
0930: ErrorManager.getDefault().notify(
0931: ErrorManager.INFORMATIONAL, t);
0932: }
0933: return false;
0934: }
0935: }
0936:
0937: public void evaluateLazily(Runnable evaluatedNotify) {
0938: Object value = "";
0939: String htmlValue = null;
0940: String nonHtmlValue = null;
0941: try {
0942: value = model.getValueAt(object, id);
0943: //System.out.println(" evaluateLazily("+TreeModelNode.this.getDisplayName()+", "+id+"): have value = "+value);
0944: if (value instanceof String) {
0945: htmlValue = htmlValue((String) value);
0946: nonHtmlValue = removeHTML((String) value);
0947: }
0948: } catch (UnknownTypeException e) {
0949: if (!(object instanceof String)) {
0950: e.printStackTrace();
0951: System.out.println(" Column id:"
0952: + columnModel.getID());
0953: System.out.println(model);
0954: System.out.println();
0955: }
0956: } catch (Throwable t) {
0957: t.printStackTrace();
0958: } finally {
0959: evaluatedNotify.run();
0960: boolean fire;
0961: synchronized (properties) {
0962: if (value instanceof String) {
0963: properties.put(id, nonHtmlValue);
0964: properties.put(id + "#html", htmlValue);
0965: } else {
0966: properties.put(id, value);
0967: }
0968: synchronized (evaluated) {
0969: fire = evaluated[0] == -1;
0970: evaluated[0] = 1;
0971: evaluated.notifyAll();
0972: }
0973: }
0974: //System.out.println("\nTreeModelNode.evaluateLazily("+TreeModelNode.this.getDisplayName()+", "+id+"): value = "+value+", fire = "+fire);
0975: if (fire) {
0976: firePropertyChange(id, null, value);
0977: refreshTheChildren(true);
0978: }
0979:
0980: }
0981: }
0982:
0983: public synchronized Object getValue() { // Sync the calls
0984: if (nodeColumn) {
0985: return TreeModelNode.this .getDisplayName();
0986: }
0987: // 1) return value from cache
0988: synchronized (properties) {
0989: //System.out.println("getValue("+TreeModelNode.this.getDisplayName()+", "+id+"): contains = "+properties.containsKey (id)+", value = "+properties.get (id));
0990: if (properties.containsKey(id))
0991: return properties.get(id);
0992: }
0993:
0994: synchronized (evaluated) {
0995: evaluated[0] = 0;
0996: }
0997: treeModelRoot.getValuesEvaluator().evaluate(this );
0998:
0999: Object ret = null;
1000: boolean refreshChildren = false;
1001:
1002: synchronized (evaluated) {
1003: if (evaluated[0] != 1) {
1004: try {
1005: evaluated.wait(25);
1006: } catch (InterruptedException iex) {
1007: }
1008: if (evaluated[0] != 1) {
1009: evaluated[0] = -1; // timeout
1010: ret = EVALUATING_STR;
1011: } else {
1012: refreshChildren = true;
1013: }
1014: }
1015: }
1016: if (ret == null) {
1017: synchronized (properties) {
1018: ret = properties.get(id);
1019: }
1020: }
1021:
1022: if (refreshChildren) {
1023: getRequestProcessor().post(new Runnable() {
1024: public void run() {
1025: refreshTheChildren(true);
1026: }
1027: });
1028: }
1029: if (ret == EVALUATING_STR && getValueType() != null
1030: && getValueType() != String.class) {
1031: ret = null; // Must not provide String when the property type is different.
1032: // htmlDisplayValue attr will assure that the Evaluating str is there.
1033: }
1034: return ret;
1035: }
1036:
1037: public Object getValue(String attributeName) {
1038: if (attributeName.equals("htmlDisplayValue")) {
1039: if (nodeColumn) {
1040: return TreeModelNode.this .getHtmlDisplayName();
1041: }
1042: synchronized (evaluated) {
1043: if (evaluated[0] != 1) {
1044: return "<html><font color=\"0000CC\">"
1045: + EVALUATING_STR + "</font></html>";
1046: }
1047: }
1048: synchronized (properties) {
1049: return properties.get(id + "#html");
1050: }
1051: }
1052: return super .getValue(attributeName);
1053: }
1054:
1055: public String getShortDescription() {
1056: if (nodeColumn) {
1057: return TreeModelNode.this .getShortDescription();
1058: }
1059: synchronized (properties) {
1060: if (!properties.containsKey(id)) {
1061: return null; // The same as value => EVALUATING_STR
1062: }
1063: }
1064: try {
1065: javax.swing.JToolTip tooltip = new javax.swing.JToolTip();
1066: tooltip
1067: .putClientProperty("getShortDescription",
1068: object); // NOI18N
1069: Object tooltipObj = model.getValueAt(tooltip, id);
1070: if (tooltipObj == null) {
1071: return null;
1072: } else {
1073: return adjustHTML(tooltipObj.toString());
1074: }
1075: } catch (UnknownTypeException e) {
1076: // Ignore models that do not define tooltips for values.
1077: return null;
1078: }
1079: }
1080:
1081: public void setValue(Object v) throws IllegalAccessException,
1082: IllegalArgumentException,
1083: java.lang.reflect.InvocationTargetException {
1084: try {
1085: model.setValueAt(object, id, v);
1086: synchronized (properties) {
1087: properties.put(id, v);
1088: }
1089: firePropertyChange(id, null, null);
1090: } catch (UnknownTypeException e) {
1091: Throwable t = ErrorManager.getDefault().annotate(
1092: e,
1093: "Column id:" + columnModel.getID()
1094: + "\nModel: " + model);
1095: ErrorManager.getDefault().notify(
1096: ErrorManager.INFORMATIONAL, t);
1097: }
1098: }
1099:
1100: public PropertyEditor getPropertyEditor() {
1101: return columnModel.getPropertyEditor();
1102: }
1103: }
1104:
1105: /** The single-threaded evaluator of lazy models. */
1106: static class LazyEvaluator implements Runnable {
1107:
1108: /** Release the evaluator task after this time. */
1109: private static final long EXPIRE_TIME = 60000L;
1110:
1111: private List<Object> objectsToEvaluate = new LinkedList<Object>();
1112: private Evaluable currentlyEvaluating;
1113: private Task evalTask;
1114:
1115: public LazyEvaluator() {
1116: evalTask = new RequestProcessor(
1117: "Debugger Values Evaluator", 1).post(this );
1118: }
1119:
1120: public void evaluate(Evaluable eval) {
1121: evaluate(eval, true);
1122: }
1123:
1124: public void evaluate(Evaluable eval, boolean checkForEvaluating) {
1125: synchronized (objectsToEvaluate) {
1126: for (Iterator it = objectsToEvaluate.iterator(); it
1127: .hasNext();) {
1128: if (eval == it.next())
1129: return; // Already scheduled
1130: }
1131: if (checkForEvaluating && currentlyEvaluating == eval)
1132: return; // Is being evaluated
1133: objectsToEvaluate.add(eval);
1134: objectsToEvaluate.notify();
1135: if (evalTask.isFinished()) {
1136: evalTask.schedule(0);
1137: }
1138: }
1139: }
1140:
1141: public void run() {
1142: while (true) {
1143: Evaluable eval;
1144: synchronized (objectsToEvaluate) {
1145: if (objectsToEvaluate.size() == 0) {
1146: try {
1147: objectsToEvaluate.wait(EXPIRE_TIME);
1148: } catch (InterruptedException iex) {
1149: return;
1150: }
1151: if (objectsToEvaluate.size() == 0) { // Expired
1152: return;
1153: }
1154: }
1155: eval = (Evaluable) objectsToEvaluate.remove(0);
1156: currentlyEvaluating = eval;
1157: }
1158: Runnable evaluatedNotify = new Runnable() {
1159: public void run() {
1160: synchronized (objectsToEvaluate) {
1161: currentlyEvaluating = null;
1162: }
1163: }
1164: };
1165: eval.evaluateLazily(evaluatedNotify);
1166: }
1167: }
1168:
1169: public interface Evaluable {
1170:
1171: public void evaluateLazily(Runnable evaluatedNotify);
1172:
1173: }
1174:
1175: }
1176:
1177: }
|