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