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: * If you wish your version of this file to be governed by only the CDDL
025: * or only the GPL Version 2, indicate your decision by adding
026: * "[Contributor] elects to include this software in this distribution
027: * under the [CDDL or GPL Version 2] license." If you do not indicate a
028: * single choice of license, a recipient has the option to distribute
029: * your version of this file under either the CDDL, the GPL Version 2 or
030: * to extend the choice of license to its licensees as provided above.
031: * However, if you add GPL Version 2 code and therefore, elected the GPL
032: * Version 2 license, then the option applies only if the new code is
033: * made subject to such option by the copyright holder.
034: *
035: * Contributor(s):
036: *
037: * Portions Copyrighted 2007 Sun Microsystems, Inc.
038: */
039:
040: package org.netbeans.api.java.source;
041:
042: import com.sun.source.tree.CompilationUnitTree;
043: import com.sun.source.tree.MethodTree;
044: import com.sun.tools.javac.api.JavacTaskImpl;
045: import com.sun.tools.javac.util.JCDiagnostic;
046: import java.io.IOException;
047: import java.util.ArrayList;
048: import java.util.Collection;
049: import java.util.Iterator;
050: import java.util.List;
051: import java.util.Locale;
052: import java.util.Map;
053: import java.util.TreeMap;
054: import java.util.logging.Level;
055: import java.util.logging.Logger;
056: import javax.swing.text.Document;
057: import javax.tools.Diagnostic;
058: import javax.tools.Diagnostic.Kind;
059: import javax.tools.DiagnosticListener;
060: import javax.tools.JavaFileObject;
061: import org.netbeans.api.lexer.TokenHierarchy;
062: import org.netbeans.modules.java.source.parsing.SourceFileObject;
063: import org.netbeans.modules.java.source.usages.Pair;
064: import org.openide.cookies.EditorCookie;
065: import org.openide.filesystems.FileObject;
066: import org.openide.loaders.DataObject;
067: import org.openide.loaders.DataObjectNotFoundException;
068: import org.openide.util.Exceptions;
069:
070: /**
071: *
072: * @author Tomas Zezula
073: */
074: final class CompilationInfoImpl {
075:
076: private JavaSource.Phase phase = JavaSource.Phase.MODIFIED;
077: private CompilationUnitTree compilationUnit;
078:
079: private JavacTaskImpl javacTask;
080: private final PositionConverter binding;
081: Pair<JavaSource.DocPositionRegion, MethodTree> changedMethod;
082: final JavaFileObject jfo;
083: final JavaSource javaSource;
084: boolean needsRestart;
085: JavaSource.Phase parserCrashed = JavaSource.Phase.UP_TO_DATE; //When javac throws an error, the moveToPhase sets this to the last safe phase
086:
087: CompilationInfoImpl(final JavaSource javaSource,
088: final PositionConverter binding,
089: final JavacTaskImpl javacTask) throws IOException {
090: assert javaSource != null;
091: this .javaSource = javaSource;
092: this .binding = binding;
093: this .jfo = this .binding != null ? javaSource.jfoProvider
094: .createJavaFileObject(binding.getFileObject(),
095: this .javaSource.rootFo, this .binding
096: .getFilter()) : null;
097: this .javacTask = javacTask;
098: }
099:
100: /**
101: * Returns the current phase of the {@link JavaSource}.
102: * @return {@link JavaSource.Phase} the state which was reached by the {@link JavaSource}.
103: */
104: JavaSource.Phase getPhase() {
105: return this .phase;
106: }
107:
108: Pair<JavaSource.DocPositionRegion, MethodTree> getChangedTree() {
109: return this .changedMethod;
110: }
111:
112: /**
113: * Returns the javac tree representing the source file.
114: * @return {@link CompilationUnitTree} the compilation unit cantaining the top level classes contained in the,
115: * java source file.
116: * @throws java.lang.IllegalStateException when the phase is less than {@link JavaSource.Phase#PARSED}
117: */
118: CompilationUnitTree getCompilationUnit() {
119: if (this .jfo == null) {
120: throw new IllegalStateException();
121: }
122: if (this .phase.compareTo(JavaSource.Phase.PARSED) < 0)
123: throw new IllegalStateException(
124: "Cannot call getCompilationInfo() if current phase < JavaSource.Phase.PARSED. You must call toPhase(Phase.PARSED) first.");//NOI18N
125: return this .compilationUnit;
126: }
127:
128: /**
129: * Returns the content of the file represented by the {@link JavaSource}.
130: * @return String the java source
131: */
132: String getText() {
133: if (this .jfo == null) {
134: throw new IllegalStateException();
135: }
136: try {
137: return this .jfo.getCharContent(false).toString();
138: } catch (IOException ioe) {
139: //Should never happen
140: Exceptions.printStackTrace(ioe);
141: return null;
142: }
143: }
144:
145: /**
146: * Returns the {@link TokenHierarchy} for the file represented by the {@link JavaSource}.
147: * @return lexer TokenHierarchy
148: */
149: TokenHierarchy<?> getTokenHierarchy() {
150: if (this .jfo == null) {
151: throw new IllegalStateException();
152: }
153: try {
154: return ((SourceFileObject) this .jfo).getTokenHierarchy();
155: } catch (IOException ioe) {
156: //Should never happen
157: Exceptions.printStackTrace(ioe);
158: return null;
159: }
160: }
161:
162: /**
163: * Returns the errors in the file represented by the {@link JavaSource}.
164: * @return an list of {@link Diagnostic}
165: */
166: List<Diagnostic> getDiagnostics() {
167: if (this .jfo == null) {
168: throw new IllegalStateException();
169: }
170: Collection<Diagnostic> errors = ((DiagnosticListenerImpl) javacTask
171: .getContext().get(DiagnosticListener.class)).errors
172: .values();
173: List<Diagnostic> partialReparseErrors = ((DiagnosticListenerImpl) javacTask
174: .getContext().get(DiagnosticListener.class)).partialReparseErrors;
175: List<Diagnostic> affectedErrors = ((DiagnosticListenerImpl) javacTask
176: .getContext().get(DiagnosticListener.class)).affectedErrors;
177: List<Diagnostic> localErrors = new ArrayList<Diagnostic>(errors
178: .size()
179: + (partialReparseErrors == null ? 0
180: : partialReparseErrors.size())
181: + (affectedErrors == null ? 0 : affectedErrors.size()));
182: localErrors.addAll(errors);
183: if (partialReparseErrors != null) {
184: localErrors.addAll(partialReparseErrors);
185: }
186: if (affectedErrors != null) {
187: localErrors.addAll(affectedErrors);
188: }
189: return localErrors;
190: }
191:
192: /**
193: * Returns {@link JavaSource} for which this {@link CompilationInfoImpl} was created.
194: * @return JavaSource
195: */
196: JavaSource getJavaSource() {
197: return javaSource;
198: }
199:
200: /**
201: * Returns {@link ClasspathInfo} for which this {@link CompilationInfoImpl} was created.
202: * @return ClasspathInfo
203: */
204: ClasspathInfo getClasspathInfo() {
205: return javaSource.getClasspathInfo();
206: }
207:
208: /**
209: * Returns the {@link FileObject} represented by this {@link CompilationInfo}.
210: * @return FileObject
211: */
212: FileObject getFileObject() {
213: return this .binding != null ? this .binding.getFileObject()
214: : null;
215: }
216:
217: /**
218: * Returns {@link Document} of this {@link CompilationInfoImpl}
219: * @return Document or null when the {@link DataObject} doesn't
220: * exist or has no {@link EditorCookie}.
221: * @throws java.io.IOException
222: */
223: public Document getDocument() {
224: final PositionConverter binding = this .getPositionConverter();
225: FileObject fo;
226: if (binding == null || (fo = binding.getFileObject()) == null) {
227: return null;
228: }
229: if (!fo.isValid()) {
230: return null;
231: }
232: try {
233: DataObject od = DataObject.find(fo);
234: EditorCookie ec = od.getCookie(EditorCookie.class);
235: if (ec != null) {
236: return ec.getDocument();
237: } else {
238: return null;
239: }
240: } catch (DataObjectNotFoundException e) {
241: //may happen when the underlying FileObject has just been deleted
242: //should be safe to ignore
243: Logger.getLogger(CompilationInfo.class.getName()).log(
244: Level.FINE, null, e);
245: return null;
246: }
247: }
248:
249: /**Return {@link PositionConverter} binding virtual Java source and the real source.
250: * Please note that this method is needed only for clients that need to work
251: * in non-Java files (eg. JSP files) or in dialogs, like code completion.
252: * Most clients do not need to use {@link PositionConverter}.
253: *
254: * @return PositionConverter binding the virtual Java source and the real source.
255: * @since 0.21
256: */
257: PositionConverter getPositionConverter() {
258: return binding;
259: }
260:
261: /**
262: * Moves the state to required phase. If given state was already reached
263: * the state is not changed. The method will throw exception if a state is
264: * illegal required. Acceptable parameters for thid method are <BR>
265: * <LI>{@link org.netbeans.api.java.source.JavaSource.Phase.PARSED}
266: * <LI>{@link org.netbeans.api.java.source.JavaSource.Phase.ELEMENTS_RESOLVED}
267: * <LI>{@link org.netbeans.api.java.source.JavaSource.Phase.RESOLVED}
268: * <LI>{@link org.netbeans.api.java.source.JavaSource.Phase.UP_TO_DATE}
269: * @param phase The required phase
270: * @return the reached state
271: * @throws IllegalArgumentException in case that given state can not be
272: * reached using this method
273: * @throws IOException when the file cannot be red
274: */
275: JavaSource.Phase toPhase(JavaSource.Phase phase) throws IOException {
276: if (phase == JavaSource.Phase.MODIFIED) {
277: throw new IllegalArgumentException("Invalid phase: "
278: + phase); //NOI18N
279: }
280: if (jfo == null) {
281: JavaSource.Phase currentPhase = getPhase();
282: if (currentPhase.compareTo(phase) < 0) {
283: setPhase(phase);
284: currentPhase = phase;
285: }
286: return currentPhase;
287: } else {
288: JavaSource.Phase currentPhase = JavaSource.moveToPhase(
289: phase, this , false);
290: return currentPhase.compareTo(phase) < 0 ? currentPhase
291: : phase;
292: }
293: }
294:
295: /**
296: * Sets the current {@link JavaSource.Phase}
297: * @param phase
298: */
299: void setPhase(final JavaSource.Phase phase) {
300: assert phase != null;
301: this .phase = phase;
302: }
303:
304: /**
305: * Sets changed method
306: * @param changedMethod
307: */
308: void setChangedMethod(
309: final Pair<JavaSource.DocPositionRegion, MethodTree> changedMethod) {
310: this .changedMethod = changedMethod;
311: }
312:
313: /**
314: * Sets the {@link CompilationUnitTree}
315: * @param compilationUnit
316: */
317: void setCompilationUnit(final CompilationUnitTree compilationUnit) {
318: assert compilationUnit != null;
319: this .compilationUnit = compilationUnit;
320: }
321:
322: /**
323: * Returns {@link JavacTaskImpl}, when it doesn't exist
324: * it's created.
325: * @return JavacTaskImpl
326: */
327: synchronized JavacTaskImpl getJavacTask() {
328: if (javacTask == null) {
329: javacTask = javaSource.createJavacTask(
330: new DiagnosticListenerImpl(this .jfo), null);
331: }
332: return javacTask;
333: }
334:
335: // Innerclasses ------------------------------------------------------------
336: static class DiagnosticListenerImpl implements DiagnosticListener {
337:
338: private final TreeMap<Integer, Diagnostic> errors;
339: private final JavaFileObject jfo;
340: private volatile List<Diagnostic> partialReparseErrors;
341: private volatile List<Diagnostic> affectedErrors;
342: private volatile int currentDelta;
343:
344: public DiagnosticListenerImpl(final JavaFileObject jfo) {
345: this .jfo = jfo;
346: this .errors = new TreeMap<Integer, Diagnostic>();
347: }
348:
349: public void report(Diagnostic message) {
350: if (this .jfo != null && this .jfo == message.getSource()) {
351: if (partialReparseErrors != null) {
352: partialReparseErrors.add(message);
353: } else {
354: errors.put((int) message.getPosition(), message);
355: }
356: }
357: }
358:
359: final boolean hasPartialReparseErrors() {
360: return this .partialReparseErrors != null
361: && !this .partialReparseErrors.isEmpty();
362: }
363:
364: final void startPartialReparse(int from, int to) {
365: if (partialReparseErrors == null) {
366: partialReparseErrors = new ArrayList<Diagnostic>();
367: this .errors.subMap(from, to).clear(); //Remove errors in changed method durring the partial reparse
368: Map<Integer, Diagnostic> tail = this .errors.tailMap(to);
369: this .affectedErrors = new ArrayList<Diagnostic>(tail
370: .size());
371: for (Iterator<Map.Entry<Integer, Diagnostic>> it = tail
372: .entrySet().iterator(); it.hasNext();) {
373: Map.Entry<Integer, Diagnostic> e = it.next();
374: it.remove();
375: this .affectedErrors.add(new D((JCDiagnostic) e
376: .getValue()));
377: }
378: } else {
379: this .partialReparseErrors.clear();
380: }
381: }
382:
383: final void endPartialReparse(final int delta) {
384: this .currentDelta += delta;
385: }
386:
387: private final class D implements Diagnostic {
388:
389: private final JCDiagnostic delegate;
390:
391: public D(final JCDiagnostic delegate) {
392: assert delegate != null;
393: this .delegate = delegate;
394: }
395:
396: public Kind getKind() {
397: return this .delegate.getKind();
398: }
399:
400: public Object getSource() {
401: return this .delegate.getSource();
402: }
403:
404: public long getPosition() {
405: long ret = this .delegate.getPosition();
406: if (delegate.hasFixedPositions()) {
407: ret += currentDelta;
408: }
409: return ret;
410: }
411:
412: public long getStartPosition() {
413: long ret = this .delegate.getStartPosition();
414: if (delegate.hasFixedPositions()) {
415: ret += currentDelta;
416: }
417: return ret;
418: }
419:
420: public long getEndPosition() {
421: long ret = this .delegate.getEndPosition();
422: if (delegate.hasFixedPositions()) {
423: ret += currentDelta;
424: }
425: return ret;
426: }
427:
428: public long getLineNumber() {
429: return -1;
430: }
431:
432: public long getColumnNumber() {
433: return -1;
434: }
435:
436: public String getCode() {
437: return this .delegate.getCode();
438: }
439:
440: public String getMessage(Locale locale) {
441: return this.delegate.getMessage(locale);
442: }
443:
444: }
445: }
446: }
|