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: package org.netbeans.modules.gsfret.source.parsing;
042:
043: import java.io.ByteArrayInputStream;
044: import java.io.ByteArrayOutputStream;
045: import java.io.IOException;
046: import java.io.InputStream;
047: import java.io.InputStreamReader;
048: import java.io.OutputStream;
049: import java.io.OutputStreamWriter;
050: import java.io.Reader;
051: import java.io.UnsupportedEncodingException;
052: import java.net.URI;
053: import java.nio.CharBuffer;
054: import java.util.Set;
055: import javax.swing.text.BadLocationException;
056: import javax.swing.text.Document;
057: import javax.swing.text.StyledDocument;
058: import org.netbeans.api.lexer.TokenHierarchy;
059: import org.netbeans.modules.gsf.Language;
060: import org.netbeans.modules.gsf.LanguageRegistry; //import org.netbeans.api.lexer.TokenHierarchy;
061: import org.openide.ErrorManager;
062: import org.openide.cookies.EditorCookie;
063: import org.openide.filesystems.FileLock;
064: import org.openide.filesystems.FileObject;
065: import org.openide.filesystems.FileStateInvalidException;
066: import org.openide.loaders.DataObject;
067: import org.openide.loaders.DataObjectNotFoundException;
068: import org.openide.text.NbDocument;
069:
070: /**
071: * This file is originally from Retouche, the Java Support
072: * infrastructure in NetBeans. I have modified the file as little
073: * as possible to make merging Retouche fixes back as simple as
074: * possible.
075: *
076: *
077: * @author Tomas Zezula
078: */
079: public class SourceFileObject/* implements DocumentProvider*/{
080:
081: final FileObject file;
082: // private final Kind kind;
083: private URI uri; //Cache for URI
084: private String text;
085: private TokenHierarchy tokens;
086:
087: public static SourceFileObject create(final FileObject file) {
088: try {
089: return new SourceFileObject(file, false);
090: } catch (IOException ioe) {
091: ErrorManager.getDefault().notify(ioe);
092: return null;
093: }
094: }
095:
096: /** Creates a new instance of SourceFileObject */
097: public SourceFileObject(final FileObject file,
098: final boolean renderNow) throws IOException {
099: assert file != null;
100: this .file = file;
101: // String ext = this.file.getExt();
102: // if (FileObjects.JAVA.equalsIgnoreCase(ext)) { //NOI18N
103: // this.kind = Kind.SOURCE;
104: // }
105: // else if (FileObjects.CLASS.equalsIgnoreCase(ext)) { //NOI18N
106: // this.kind = Kind.CLASS;
107: // }
108: // else if (FileObjects.HTML.equalsIgnoreCase(ext)) { //NOI18N
109: // this.kind = Kind.HTML;
110: // }
111: // else {
112: // this.kind = Kind.OTHER;
113: // }
114: if (renderNow) {
115: text = getCharContentImpl().toString();
116: }
117: }
118:
119: // public boolean isNameCompatible (String simplename, JavaFileObject.Kind kind) {
120: // assert simplename != null;
121: // return this.kind == kind && this.getNameWithoutExtension().equals(simplename);
122: // }
123:
124: public CharBuffer getCharContent(boolean ignoreEncodingErrors)
125: throws IOException {
126: String _text;
127: synchronized (this ) {
128: _text = this .text;
129: }
130: if (_text != null) {
131: return CharBuffer.wrap(_text);
132: } else {
133: return getCharContentImpl();
134: }
135: }
136:
137: public TokenHierarchy getTokenHierarchy() throws IOException {
138: if (tokens == null)
139: getCharContentImpl();
140:
141: return tokens;
142: }
143:
144: public java.io.Writer openWriter() throws IOException {
145: return new OutputStreamWriter(this .openOutputStream(),
146: encodingName);
147: }
148:
149: public Reader openReader(boolean ignoreEncodingErrors)
150: throws IOException {
151: return new InputStreamReader(this .openInputStream(),
152: encodingName);
153: }
154:
155: static final String encodingName = new OutputStreamWriter(
156: new ByteArrayOutputStream()).getEncoding();
157:
158: public java.io.OutputStream openOutputStream() throws IOException {
159: final StyledDocument doc = getDocument(isOpened());
160: if (doc == null) {
161: return new LckStream(this .file);
162: } else {
163: return new DocumentStream(doc);
164: }
165: }
166:
167: public InputStream openInputStream() throws IOException {
168: String _text;
169: synchronized (this ) {
170: _text = text;
171: }
172: if (_text != null) {
173: return new ByteArrayInputStream(_text.getBytes());
174: } else {
175: final Document doc = getDocument(isOpened());
176: if (doc == null) {
177: return this .file.getInputStream();
178: } else {
179: final StringBuilder builder = new StringBuilder();
180: doc.render(new Runnable() {
181: public void run() {
182: try {
183: builder.append(doc.getText(0, doc
184: .getLength()));
185: } catch (BadLocationException e) {
186: ErrorManager.getDefault().notify(e);
187: }
188: }
189: });
190: return new ByteArrayInputStream(builder.toString()
191: .getBytes());
192: }
193: }
194: }
195:
196: public boolean delete() {
197: if (isModified() != null) {
198: //If the file is modified in editor do not delete it
199: return false;
200: } else {
201: try {
202: FileLock lock = this .file.lock();
203: try {
204: this .file.delete(lock);
205: return true;
206: } finally {
207: lock.releaseLock();
208: }
209: } catch (IOException e) {
210: return false;
211: }
212: }
213: }
214:
215: // public JavaFileObject.Kind getKind() {
216: // return this.kind;
217: // }
218: //
219: public String getName() {
220: return this .file.getNameExt();
221: }
222:
223: public String getNameWithoutExtension() {
224: return this .file.getName();
225: }
226:
227: public synchronized URI toUri() {
228: if (this .uri == null) {
229: try {
230: this .uri = URI.create(this .file.getURL()
231: .toExternalForm());
232: } catch (FileStateInvalidException e) {
233: ErrorManager.getDefault().notify(e);
234: }
235: }
236: return this .uri;
237: }
238:
239: /**
240: * Returns the mtime of the file, in the case of opened
241: * modified file, the mtime is not known, this method returns
242: * the current time.
243: */
244: public long getLastModified() {
245: if (isModified() == null) {
246: return this .file.lastModified().getTime();
247: } else {
248: return System.currentTimeMillis();
249: }
250: }
251:
252: // public NestingKind getNestingKind() {
253: // return null;
254: // }
255: //
256: // public Modifier getAccessLevel() {
257: // return null;
258: // }
259: //
260: // public @Override String toString () {
261: // return this.file.getPath();
262: // }
263:
264: public @Override
265: boolean equals(Object other) {
266: if (other instanceof SourceFileObject) {
267: SourceFileObject otherSource = (SourceFileObject) other;
268: return this .file.equals(otherSource.file);
269: } else {
270: return false;
271: }
272: }
273:
274: public @Override
275: int hashCode() {
276: return this .file.hashCode();
277: }
278:
279: public StyledDocument getDocument() {
280: EditorCookie ec = isOpened();
281: return ec != null ? getDocument(ec) : null;
282: }
283:
284: public void runAtomic(final Runnable r) {
285: assert r != null;
286: final StyledDocument doc = getDocument();
287: if (doc == null) {
288: throw new IllegalStateException();
289: } else {
290: NbDocument.runAtomic(doc, r);
291: }
292: }
293:
294: @SuppressWarnings("unchecked")
295: // NOI18N
296: private EditorCookie isModified() {
297: DataObject.Registry regs = DataObject.getRegistry();
298: Set<DataObject> modified = (Set<DataObject>) regs
299: .getModifiedSet();
300: for (DataObject dobj : modified) {
301: if (this .file.equals(dobj.getPrimaryFile())) {
302: EditorCookie ec = (EditorCookie) dobj
303: .getCookie(EditorCookie.class);
304: return ec;
305: }
306: }
307: return null;
308: }
309:
310: public EditorCookie isOpened() {
311: //if (this.kind == JavaFileObject.Kind.CLASS) {
312: // return null;
313: //}
314: try {
315: DataObject dobj = DataObject.find(this .file);
316: return (EditorCookie) dobj.getCookie(EditorCookie.class);
317: } catch (DataObjectNotFoundException dnf) {
318: return null;
319: }
320: }
321:
322: private CharBuffer getCharContentImpl() throws IOException {
323: final Document doc = getDocument(isOpened());
324: final char[][] result = new char[1][];
325: final int[] length = new int[1];
326: if (doc == null) {
327: Reader in = this .openReader(true);
328: int red = 0, rv;
329: try {
330: int len = (int) this .file.getSize();
331: result[0] = new char[len + 1];
332: while ((rv = in.read(result[0], red, len - red)) > 0
333: && (red = red + rv) < len)
334: ;
335: } finally {
336: in.close();
337: }
338: int j = 0;
339: for (int i = 0; i < red; i++) {
340: if (result[0][i] == '\r') { //NOI18N
341: if (i + 1 >= red || result[0][i + 1] != '\n') { //NOI18N
342: result[0][j++] = '\n'; //NOI18N
343: }
344: } else {
345: result[0][j++] = result[0][i];
346: }
347: }
348: length[0] = j;
349: } else {
350: doc.render(new Runnable() {
351: public void run() {
352: try {
353: int len = doc.getLength();
354: result[0] = new char[len + 1];
355: doc.getText(0, len).getChars(0, len, result[0],
356: 0);
357: length[0] = len;
358: } catch (BadLocationException e) {
359: ErrorManager.getDefault().notify(e);
360: }
361: }
362: });
363: }
364: result[0][length[0]] = '\n'; //NOI18N
365: CharBuffer charBuffer = CharBuffer
366: .wrap(result[0], 0, length[0]);
367: Language language = LanguageRegistry.getInstance()
368: .getLanguageByMimeType(this .file.getMIMEType());
369: if (language != null && language.getGsfLanguage() != null) {
370: org.netbeans.api.lexer.Language lexerLanguage = (org.netbeans.api.lexer.Language) language
371: .getGsfLanguage().getLexerLanguage();
372: tokens = TokenHierarchy.create(charBuffer, true,
373: lexerLanguage, null, null); //TODO: .createSnapshot();
374: }
375: return charBuffer;
376: }
377:
378: private static StyledDocument getDocument(EditorCookie ec) {
379: return ec == null ? null : ec.getDocument();
380: }
381:
382: private class LckStream extends OutputStream {
383:
384: private final OutputStream delegate;
385: private final FileLock lock;
386:
387: public LckStream(final FileObject fo) throws IOException {
388: assert fo != null;
389: this .lock = fo.lock();
390: try {
391: this .delegate = fo.getOutputStream(this .lock);
392: } finally {
393: if (this .delegate == null) {
394: this .lock.releaseLock();
395: }
396: }
397: }
398:
399: public @Override
400: void write(byte[] b, int off, int len) throws IOException {
401: this .delegate.write(b, off, len);
402: }
403:
404: public @Override
405: void write(byte[] b) throws IOException {
406: this .delegate.write(b);
407: }
408:
409: public void write(int b) throws IOException {
410: this .delegate.write(b);
411: }
412:
413: public @Override
414: void close() throws IOException {
415: try {
416: this .delegate.close();
417: } finally {
418: this .lock.releaseLock();
419: synchronized (SourceFileObject.this ) {
420: text = null;
421: }
422: }
423: }
424: }
425:
426: private class DocumentStream extends OutputStream {
427:
428: private static final int BUF_SIZ = 2048;
429:
430: private final StyledDocument doc;
431: private byte[] data;
432: private int pos;
433:
434: public DocumentStream(final StyledDocument doc) {
435: assert doc != null;
436: this .doc = doc;
437: this .data = new byte[BUF_SIZ];
438: this .pos = 0;
439: }
440:
441: public synchronized @Override
442: void write(byte[] b, int off, int len) throws IOException {
443: ensureSize(len);
444: System.arraycopy(b, off, this .data, this .pos, len);
445: this .pos += len;
446: }
447:
448: public synchronized @Override
449: void write(byte[] b) throws IOException {
450: ensureSize(b.length);
451: System.arraycopy(b, 0, this .data, this .pos, b.length);
452: this .pos += b.length;
453: }
454:
455: public synchronized void write(int b) throws IOException {
456: ensureSize(1);
457: this .data[this .pos++] = (byte) (b & 0xff);
458: }
459:
460: private void ensureSize(int delta) {
461: int requiredLength = this .pos + delta;
462: if (this .data.length < requiredLength) {
463: int newSize = this .data.length + BUF_SIZ;
464: while (newSize < requiredLength) {
465: newSize += BUF_SIZ;
466: }
467: byte[] newData = new byte[newSize];
468: System.arraycopy(this .data, 0, newData, 0, this .pos);
469: this .data = newData;
470: }
471: }
472:
473: public synchronized @Override
474: void close() throws IOException {
475: try {
476: NbDocument.runAtomic(this .doc, new Runnable() {
477: public void run() {
478: try {
479: doc.remove(0, doc.getLength());
480: doc.insertString(0, new String(data, 0,
481: pos, encodingName), null);
482: } catch (BadLocationException e) {
483: ErrorManager.getDefault().notify(e);
484: } catch (UnsupportedEncodingException ee) {
485: ErrorManager.getDefault().notify(ee);
486: }
487: }
488: });
489: } finally {
490: synchronized (SourceFileObject.this) {
491: text = null;
492: }
493: }
494: }
495: }
496: }
|