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.apps.index;
043:
044: import java.io.IOException;
045: import java.util.ArrayList;
046: import java.util.Collections;
047: import java.util.HashMap;
048: import java.util.LinkedList;
049: import java.util.List;
050: import java.util.Map;
051: import java.util.WeakHashMap;
052: import java.util.logging.Level;
053: import java.util.logging.Logger;
054: import javax.swing.event.ChangeEvent;
055: import javax.swing.event.ChangeListener;
056: import org.openide.util.WeakListeners;
057: import org.w3c.dom.Document;
058: import org.w3c.dom.Element;
059: import org.w3c.dom.NodeList;
060: import threaddemo.data.DomProvider;
061: import threaddemo.data.PhadhailLookups;
062: import threaddemo.locking.RWLock;
063: import threaddemo.model.Phadhail;
064: import threaddemo.model.PhadhailEvent;
065: import threaddemo.model.PhadhailListener;
066: import threaddemo.model.PhadhailNameEvent;
067:
068: // XXX make an IndexImpl be GCable and not hold onto Phadhail's
069:
070: /**
071: * Actual implementation of the index.
072: * @author Jesse Glick
073: */
074: final class IndexImpl implements Index, Runnable, PhadhailListener,
075: ChangeListener {
076:
077: private static final Logger logger = Logger
078: .getLogger(IndexImpl.class.getName());
079:
080: private final Phadhail root;
081: private final List<ChangeListener> listeners = new ArrayList<ChangeListener>();
082: private boolean running = false;
083: private final LinkedList<Phadhail> toProcess = new LinkedList<Phadhail>();
084: private final Map<Phadhail, Map<String, Integer>> processed = new /*Weak*/HashMap<Phadhail, Map<String, Integer>>();
085: private final Map<DomProvider, Phadhail> domProviders2Phadhails = new WeakHashMap<DomProvider, Phadhail>();
086: private final Map<Phadhail, Phadhail> phadhails2Parents = new WeakHashMap<Phadhail, Phadhail>();
087:
088: public IndexImpl(Phadhail root) {
089: this .root = root;
090: }
091:
092: public RWLock getLock() {
093: return root.lock();
094: }
095:
096: public Map<String, Integer> getData() {
097: assert getLock().canRead();
098: Map<String, Integer> data = processed.get(root);
099: if (data != null) {
100: return Collections.unmodifiableMap(data);
101: } else {
102: return Collections.emptyMap();
103: }
104: }
105:
106: public Phadhail getRoot() {
107: return root;
108: }
109:
110: public void addChangeListener(final ChangeListener l) {
111: synchronized (listeners) {
112: listeners.add(l);
113: }
114: }
115:
116: public void removeChangeListener(ChangeListener l) {
117: synchronized (listeners) {
118: listeners.remove(l);
119: }
120: }
121:
122: private void fireChange() {
123: List<ChangeListener> ls;
124: synchronized (listeners) {
125: if (listeners.isEmpty()) {
126: return;
127: }
128: ls = new ArrayList<ChangeListener>(listeners);
129: }
130: ChangeEvent ev = new ChangeEvent(this );
131: for (ChangeListener l : ls) {
132: l.stateChanged(ev);
133: }
134: }
135:
136: public void start() {
137: synchronized (toProcess) {
138: if (!running) {
139: toProcess.add(root);
140: Thread t = new Thread(this , "IndexImpl parsing: "
141: + root);
142: t.setDaemon(true);
143: t.start();
144: running = true;
145: }
146: }
147: }
148:
149: public void cancel() {
150: synchronized (toProcess) {
151: running = false;
152: }
153: }
154:
155: public void run() {
156: while (true) {
157: final Phadhail next;
158: synchronized (toProcess) {
159: if (!running) {
160: break;
161: }
162: while (toProcess.isEmpty()) {
163: try {
164: toProcess.wait();
165: } catch (InterruptedException e) {
166: assert false : e;
167: }
168: }
169: next = toProcess.removeFirst();
170: }
171: process(next);
172: }
173: }
174:
175: private void process(final Phadhail ph) {
176: getLock().read(new Runnable() {
177: public void run() {
178: if (processed.containsKey(ph)) {
179: // Already computed, do nothing.
180: return;
181: }
182: if (ph.hasChildren()) {
183: processChildren(ph);
184: } else {
185: // Data, maybe.
186: final Map<String, Integer> computed = compute(ph);
187: getLock().writeLater(new Runnable() {
188: public void run() {
189: processed.put(ph, computed);
190: if (!computed.isEmpty()) {
191: bubble(ph);
192: }
193: }
194: });
195: }
196: }
197: });
198: }
199:
200: private void processChildren(Phadhail ph) {
201: synchronized (toProcess) {
202: for (Phadhail kid : ph.getChildren()) {
203: phadhails2Parents.put(kid, ph);
204: if (!toProcess.contains(kid)) {
205: toProcess.add(kid);
206: }
207: }
208: toProcess.notify();
209: }
210: // XXX use WeakListener instead? ... not if Index is long-lived though
211: ph.removePhadhailListener(this );
212: ph.addPhadhailListener(this );
213: }
214:
215: private int count;
216:
217: private Map<String, Integer> compute(Phadhail ph) {
218: assert getLock().canRead();
219: assert !ph.hasChildren();
220: logger.log(Level.FINER, "Computing index for {0} [#{1}]",
221: new Object[] { ph, ++count });
222: // XXX technically should listen to lookup changes...
223: DomProvider p = (DomProvider) PhadhailLookups.getLookup(ph)
224: .lookup(DomProvider.class);
225: if (p == null) {
226: logger.finer("no DomProvider here");
227: return Collections.emptyMap();
228: }
229: domProviders2Phadhails.put(p, ph);
230: Document d;
231: try {
232: d = p.getDocument();
233: } catch (IOException e) {
234: logger.log(Level.FINE, "Parsing failed for {0}: {1}",
235: new Object[] { ph.getName(), e.getMessage() });
236: return Collections.emptyMap();
237: }
238: // Wait till after p.getDocument(), since that will fire stateChanged
239: // the first time it is called (not ready -> ready)
240: p.addChangeListener(WeakListeners.change(this , p));
241: Map<String, Integer> m = new HashMap<String, Integer>();
242: NodeList l = d.getElementsByTagName("*");
243: for (int i = 0; i < l.getLength(); i++) {
244: String name = ((Element) l.item(i)).getTagName();
245: Integer old = m.get(name);
246: m.put(name, old != null ? old + 1 : 1);
247: }
248: logger.log(Level.FINER, "Parse succeeded for {0}", ph);
249: logger.log(Level.FINEST, "Parse results: {0}", m);
250: return m;
251: }
252:
253: private void bubble(Phadhail ph) {
254: assert getLock().canWrite();
255: logger.log(Level.FINER, "bubble: {0} data size: {1}",
256: new Object[] { ph, processed.size() });
257: logger.log(Level.FINEST, "bubble: {0} data: {1}", new Object[] {
258: ph, processed });
259: if (ph == root) {
260: getLock().read(new Runnable() {
261: public void run() {
262: fireChange();
263: }
264: });
265: } else {
266: Phadhail parent = (Phadhail) phadhails2Parents.get(ph);
267: assert parent != null : ph;
268: assert parent.hasChildren();
269: Map<String, Integer> recalc = new HashMap<String, Integer>();
270: for (Phadhail kid : parent.getChildren()) {
271: Map<String, Integer> subdata = processed.get(kid);
272: if (subdata == null) {
273: // OK, kid is simply not yet calculated, will bubble changes later.
274: continue;
275: }
276: for (Map.Entry<String, Integer> e : subdata.entrySet()) {
277: String name = e.getKey();
278: int x1 = e.getValue();
279: if (recalc.containsKey(name)) {
280: recalc.put(name, x1 + recalc.get(name));
281: } else {
282: recalc.put(name, x1);
283: }
284: }
285: }
286: processed.put(parent, recalc);
287: bubble(parent);
288: }
289: }
290:
291: private void invalidate(final Phadhail ph) {
292: getLock().writeLater(new Runnable() {
293: public void run() {
294: processed.remove(ph);
295: synchronized (toProcess) {
296: if (!toProcess.contains(ph)) {
297: toProcess.add(ph);
298: toProcess.notify();
299: }
300: }
301: }
302: });
303: }
304:
305: public void childrenChanged(PhadhailEvent ev) {
306: Phadhail ph = ev.getPhadhail();
307: logger.log(Level.FINER, "childrenChanged: {0}", ph);
308: invalidate(ph);
309: }
310:
311: public void nameChanged(PhadhailNameEvent ev) {
312: // ignore
313: }
314:
315: public void stateChanged(ChangeEvent e) {
316: DomProvider p = (DomProvider) e.getSource();
317: Phadhail ph = domProviders2Phadhails.get(p);
318: assert ph != null;
319: logger.log(Level.FINER, "stateChanged: {0}", ph);
320: invalidate(ph);
321: }
322:
323: }
|