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.data;
043:
044: import java.io.ByteArrayInputStream;
045: import java.io.ByteArrayOutputStream;
046: import java.io.IOException;
047: import java.io.StringReader;
048: import java.io.UnsupportedEncodingException;
049: import java.lang.reflect.InvocationTargetException;
050: import java.util.ArrayList;
051: import java.util.HashSet;
052: import java.util.List;
053: import java.util.Set;
054: import java.util.logging.Level;
055: import java.util.logging.Logger;
056: import javax.swing.event.ChangeEvent;
057: import javax.swing.event.ChangeListener;
058: import javax.swing.event.DocumentEvent;
059: import javax.swing.text.BadLocationException;
060: import javax.swing.text.StyledDocument;
061: import org.openide.cookies.EditorCookie;
062: import org.openide.xml.XMLUtil;
063: import org.w3c.dom.Document;
064: import org.w3c.dom.events.Event;
065: import org.w3c.dom.events.EventListener;
066: import org.w3c.dom.events.EventTarget;
067: import org.xml.sax.EntityResolver;
068: import org.xml.sax.ErrorHandler;
069: import org.xml.sax.InputSource;
070: import org.xml.sax.SAXException;
071: import org.xml.sax.SAXParseException;
072: import threaddemo.locking.RWLock;
073: import threaddemo.locking.LockAction;
074: import threaddemo.locking.LockExceptionAction;
075: import threaddemo.model.Phadhail;
076: import threaddemo.util.ClobberException;
077: import threaddemo.util.DocumentParseSupport;
078: import threaddemo.util.DocumentParseSupportDelta;
079: import threaddemo.util.TwoWayEvent;
080: import threaddemo.util.TwoWayListener;
081:
082: // XXX should maybe show stale value during delays
083:
084: /**
085: * Support class for DOM provider interface.
086: * <p>The derived model is a {@link Document}. The deltas to the derived model
087: * are the same {@link Document}s - this class does not model structural diffs
088: * using the {@link TwoWaySupport} semantics.
089: * @author Jesse Glick
090: */
091: public final class DomSupport extends
092: DocumentParseSupport<Document, Document> implements
093: DomProvider, ErrorHandler,
094: TwoWayListener<Document, DocumentParseSupportDelta, Document>,
095: EntityResolver, EventListener {
096:
097: private static final Logger logger = Logger
098: .getLogger(DomSupport.class.getName());
099:
100: private final Phadhail ph;
101: private final Set<ChangeListener> listeners = new HashSet<ChangeListener>();
102: private boolean inIsolatingChange = false;
103: private boolean madeIsolatedChanges;
104:
105: public DomSupport(Phadhail ph, EditorCookie.Observable edit,
106: RWLock lock) {
107: super (edit, lock);
108: this .ph = ph;
109: addTwoWayListener(this );
110: }
111:
112: public Document getDocument() throws IOException {
113: return getLock().read(
114: new LockExceptionAction<Document, IOException>() {
115: public Document run() throws IOException {
116: assert !inIsolatingChange;
117: try {
118: Document v = getValueBlocking();
119: logger.log(Level.FINER, "getDocument: {0}",
120: v);
121: return v;
122: } catch (InvocationTargetException e) {
123: throw (IOException) e.getCause();
124: }
125: }
126: });
127: }
128:
129: public void setDocument(final Document d) throws IOException {
130: if (d == null)
131: throw new NullPointerException();
132: getLock().write(new LockExceptionAction<Void, IOException>() {
133: public Void run() throws IOException {
134: assert !inIsolatingChange;
135: Document old = (Document) getStaleValueNonBlocking();
136: if (old != null && old != d) {
137: ((EventTarget) old).removeEventListener(
138: "DOMSubtreeModified", DomSupport.this ,
139: false);
140: ((EventTarget) d).addEventListener(
141: "DOMSubtreeModified", DomSupport.this ,
142: false);
143: }
144: try {
145: mutate(d);
146: return null;
147: } catch (InvocationTargetException e) {
148: throw (IOException) e.getCause();
149: } catch (ClobberException e) {
150: throw (IOException) new IOException(e.toString())
151: .initCause(e);
152: }
153: }
154: });
155: }
156:
157: public boolean isReady() {
158: return getLock().read(new LockAction<Boolean>() {
159: public Boolean run() {
160: assert !inIsolatingChange;
161: return getValueNonBlocking() != null;
162: }
163: });
164: }
165:
166: public void start() {
167: initiate();
168: }
169:
170: public RWLock lock() {
171: return getLock();
172: }
173:
174: public void addChangeListener(ChangeListener l) {
175: synchronized (listeners) {
176: listeners.add(l);
177: }
178: }
179:
180: public void removeChangeListener(ChangeListener l) {
181: synchronized (listeners) {
182: listeners.remove(l);
183: }
184: }
185:
186: private void fireChange() {
187: final List<ChangeListener> ls;
188: synchronized (listeners) {
189: if (listeners.isEmpty()) {
190: logger.log(Level.FINER,
191: "DomSupport change with no listeners: {0}", ph);
192: return;
193: }
194: ls = new ArrayList<ChangeListener>(listeners);
195: }
196: final ChangeEvent ev = new ChangeEvent(this );
197: getLock().read(new Runnable() {
198: public void run() {
199: assert !inIsolatingChange;
200: logger.log(Level.FINER, "DomSupport change: {0}", ph);
201: for (ChangeListener l : ls) {
202: l.stateChanged(ev);
203: }
204: }
205: });
206: }
207:
208: protected boolean requiresUnmodifiedDocument() {
209: return false;
210: }
211:
212: protected final DerivationResult<Document, Document> doDerive(
213: StyledDocument document,
214: List<DocumentEvent> documentEvents, Document oldValue)
215: throws IOException {
216: assert !inIsolatingChange;
217: // ignoring documentEvents
218: logger.log(Level.FINER, "DomSupport doDerive: {0}", ph);
219: if (oldValue != null) {
220: ((EventTarget) oldValue).removeEventListener(
221: "DOMSubtreeModified", this , false);
222: }
223: InputSource source;
224: if (document != null) {
225: String text;
226: try {
227: text = document.getText(0, document.getLength());
228: } catch (BadLocationException e) {
229: assert false : e;
230: text = "";
231: }
232: source = new InputSource(new StringReader(text));
233: } else {
234: // From disk.
235: source = new InputSource(ph.getInputStream());
236: }
237: Document newValue;
238: try {
239: newValue = XMLUtil.parse(source, false, false, this , this );
240: } catch (SAXException e) {
241: throw (IOException) new IOException(e.toString())
242: .initCause(e);
243: }
244: ((EventTarget) newValue).addEventListener("DOMSubtreeModified",
245: this , false);
246: // This impl does not compute structural diffs, so newValue == derivedDelta when modified:
247: return new DerivationResult<Document, Document>(newValue,
248: oldValue != null ? newValue : null);
249: }
250:
251: protected final Document doRecreate(StyledDocument document,
252: Document oldValue, Document newDom) throws IOException {
253: assert !inIsolatingChange;
254: logger.log(Level.FINER, "DomSupport doRecreate: {0}", ph);
255: // ignoring oldValue, returning same newDom
256: ByteArrayOutputStream baos = new ByteArrayOutputStream();
257: try {
258: XMLUtil.write(newDom, baos, "UTF-8");
259: } catch (IOException ioe) {
260: assert false : ioe;
261: throw ioe;
262: }
263: try {
264: document.remove(0, document.getLength());
265: } catch (BadLocationException e) {
266: assert false : e;
267: }
268: try {
269: document.insertString(0, baos.toString("UTF-8"), null);
270: } catch (UnsupportedEncodingException e) {
271: assert false : e;
272: throw e;
273: } catch (BadLocationException e) {
274: assert false : e;
275: }
276: return newDom;
277: }
278:
279: protected long delay() {
280: return 5000L;
281: }
282:
283: public String toString() {
284: return "DomSupport[" + ph + "]";
285: }
286:
287: public void error(SAXParseException exception) throws SAXException {
288: throw exception;
289: }
290:
291: public void fatalError(SAXParseException exception)
292: throws SAXException {
293: throw exception;
294: }
295:
296: public void warning(SAXParseException exception)
297: throws SAXException {
298: throw exception;
299: }
300:
301: public InputSource resolveEntity(String publicId, String systemId)
302: throws SAXException, IOException {
303: // Ignore external entities.
304: return new InputSource(new ByteArrayInputStream(new byte[0]));
305: }
306:
307: public void broken(TwoWayEvent.Broken evt) {
308: logger.log(Level.FINER, "Received: {0}", evt);
309: fireChange();
310: }
311:
312: public void clobbered(TwoWayEvent.Clobbered evt) {
313: logger.log(Level.FINER, "Received: {0}", evt);
314: assert false;
315: }
316:
317: public void derived(TwoWayEvent.Derived evt) {
318: logger.log(Level.FINER, "Received: {0}", evt);
319: fireChange();
320: }
321:
322: public void forgotten(TwoWayEvent.Forgotten evt) {
323: logger.log(Level.FINER, "Received: {0}", evt);
324: assert false;
325: }
326:
327: public void invalidated(TwoWayEvent.Invalidated evt) {
328: logger.log(Level.FINER, "Received: {0}", evt);
329: // just wait...
330: initiate();
331: }
332:
333: public void recreated(TwoWayEvent.Recreated evt) {
334: logger.log(Level.FINER, "Received: {0}", evt);
335: fireChange();
336: }
337:
338: public void handleEvent(final Event evt) {
339: try {
340: getLock().write(new Runnable() {
341: public void run() {
342: Document d = (Document) evt.getCurrentTarget();
343: Document old = (Document) getValueNonBlocking();
344: assert old == null || old == d;
345: logger
346: .log(
347: Level.FINEST,
348: "DomSupport got DOM event {0} on {1}, inIsolatingChange={2}",
349: new Object[] {
350: evt,
351: ph,
352: inIsolatingChange ? Boolean.TRUE
353: : Boolean.FALSE });
354: if (!inIsolatingChange) {
355: try {
356: setDocument(d);
357: } catch (IOException e) {
358: assert false : e;
359: }
360: } else {
361: madeIsolatedChanges = true;
362: }
363: }
364: });
365: } catch (RuntimeException e) {
366: // Xerces ignores them.
367: e.printStackTrace();
368: }
369: }
370:
371: public void isolatingChange(Runnable r) {
372: assert getLock().canWrite();
373: assert !inIsolatingChange;
374: madeIsolatedChanges = false;
375: inIsolatingChange = true;
376: try {
377: r.run();
378: } finally {
379: inIsolatingChange = false;
380: logger
381: .log(
382: Level.FINER,
383: "Finished isolatingChange on {0}; madeIsolatedChanges={1}",
384: new Object[] {
385: ph,
386: madeIsolatedChanges ? Boolean.TRUE
387: : Boolean.FALSE });
388: if (madeIsolatedChanges) {
389: Document d = getValueNonBlocking();
390: if (d != null) {
391: try {
392: setDocument(d);
393: } catch (IOException e) {
394: assert false : e;
395: }
396: } else {
397: // ???
398: fireChange();
399: }
400: }
401: }
402: }
403:
404: }
|