0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.api.java.source;
0043:
0044: import com.sun.source.tree.ClassTree;
0045: import com.sun.source.tree.CompilationUnitTree;
0046: import com.sun.source.tree.MethodTree;
0047: import com.sun.source.tree.Tree;
0048: import com.sun.source.util.SimpleTreeVisitor;
0049: import com.sun.source.util.SourcePositions;
0050: import com.sun.source.util.TreePath;
0051: import com.sun.source.util.TreeScanner;
0052: import com.sun.source.util.Trees;
0053: import com.sun.tools.javac.api.ClassNamesForFileOraculum;
0054: import com.sun.tools.javac.api.JavacTaskImpl;
0055: import com.sun.tools.javac.api.JavacTrees;
0056: import com.sun.tools.javac.code.Source;
0057: import com.sun.tools.javac.code.Symbol.CompletionFailure;
0058: import com.sun.tools.javac.comp.AttrContext;
0059: import com.sun.tools.javac.comp.Enter;
0060: import com.sun.tools.javac.comp.Env;
0061: import com.sun.tools.javac.parser.DocCommentScanner;
0062: import com.sun.tools.javac.tree.JCTree;
0063: import com.sun.tools.javac.tree.JCTree.JCBlock;
0064: import com.sun.tools.javac.tree.JCTree.JCClassDecl;
0065: import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
0066: import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
0067: import com.sun.tools.javac.util.Abort;
0068: import com.sun.tools.javac.util.CancelAbort;
0069: import com.sun.tools.javac.util.CancelService;
0070: import com.sun.tools.javac.util.Context;
0071: import com.sun.tools.javac.util.CouplingAbort;
0072: import com.sun.tools.javac.util.FlowListener;
0073: import com.sun.tools.javac.util.Log;
0074: import com.sun.tools.javadoc.JavadocEnter;
0075: import com.sun.tools.javadoc.JavadocMemberEnter;
0076: import com.sun.tools.javadoc.Messager;
0077: import java.beans.PropertyChangeEvent;
0078: import java.beans.PropertyChangeListener;
0079: import java.io.File;
0080: import java.io.FileOutputStream;
0081: import java.io.IOException;
0082: import java.io.InterruptedIOException;
0083: import java.io.OutputStream;
0084: import java.io.OutputStreamWriter;
0085: import java.io.PrintWriter;
0086: import java.io.Writer;
0087: import java.lang.ref.Reference;
0088: import java.lang.ref.WeakReference;
0089: import java.lang.reflect.Method;
0090: import java.net.URL;
0091: import java.util.ArrayList;
0092: import java.util.Arrays;
0093: import java.util.Collection;
0094: import java.util.Collections;
0095: import java.util.Comparator;
0096: import java.util.HashMap;
0097: import java.util.HashSet;
0098: import java.util.Iterator;
0099: import java.util.LinkedList;
0100: import java.util.List;
0101: import java.util.Map;
0102: import java.util.Set;
0103: import java.util.WeakHashMap;
0104: import java.util.concurrent.CountDownLatch;
0105: import java.util.concurrent.ExecutionException;
0106: import java.util.concurrent.Executors;
0107: import java.util.concurrent.Future;
0108: import java.util.concurrent.PriorityBlockingQueue;
0109: import java.util.concurrent.ThreadFactory;
0110: import java.util.concurrent.TimeUnit;
0111: import java.util.concurrent.TimeoutException;
0112: import java.util.concurrent.atomic.AtomicBoolean;
0113: import java.util.concurrent.atomic.AtomicReference;
0114: import java.util.concurrent.locks.ReentrantLock;
0115: import java.util.logging.Level;
0116: import java.util.logging.Logger;
0117: import java.util.regex.Pattern;
0118: import java.util.regex.PatternSyntaxException;
0119: import javax.swing.event.CaretEvent;
0120: import javax.swing.event.CaretListener;
0121: import javax.swing.event.ChangeEvent;
0122: import javax.swing.event.ChangeListener;
0123: import javax.swing.text.AbstractDocument;
0124: import javax.swing.text.BadLocationException;
0125: import javax.swing.text.Document;
0126: import javax.swing.text.JTextComponent;
0127: import javax.swing.text.StyledDocument;
0128: import javax.tools.Diagnostic;
0129: import javax.tools.DiagnosticListener;
0130: import javax.tools.JavaCompiler;
0131: import javax.tools.JavaFileObject;
0132: import javax.tools.ToolProvider;
0133: import org.netbeans.api.editor.EditorRegistry;
0134: import org.netbeans.api.java.classpath.ClassPath;
0135: import org.netbeans.api.java.lexer.JavaTokenId;
0136: import org.netbeans.api.java.platform.JavaPlatformManager;
0137: import org.netbeans.api.java.queries.SourceLevelQuery;
0138: import org.netbeans.api.java.source.ClasspathInfo.PathKind;
0139: import org.netbeans.api.java.source.ModificationResult.Difference;
0140: import org.netbeans.api.lexer.TokenChange;
0141: import org.netbeans.api.lexer.TokenHierarchy;
0142: import org.netbeans.api.lexer.TokenHierarchyEvent;
0143: import org.netbeans.api.lexer.TokenHierarchyEventType;
0144: import org.netbeans.api.lexer.TokenHierarchyListener;
0145: import org.netbeans.api.lexer.TokenSequence;
0146: import org.netbeans.lib.editor.util.swing.PositionRegion;
0147: import org.netbeans.modules.java.source.JavaFileFilterQuery;
0148: import org.netbeans.modules.java.source.JavaSourceAccessor;
0149: import org.netbeans.modules.java.source.JavadocEnv;
0150: import org.netbeans.modules.java.source.parsing.FileObjects;
0151: import org.netbeans.modules.java.preprocessorbridge.spi.JavaFileFilterImplementation;
0152: import org.netbeans.modules.java.preprocessorbridge.spi.JavaSourceProvider;
0153: import org.netbeans.modules.java.source.TreeLoader;
0154: import org.netbeans.modules.java.source.parsing.SourceFileObject;
0155: import org.netbeans.modules.java.source.tasklist.CompilerSettings;
0156: import org.netbeans.modules.java.source.usages.ClassIndexImpl;
0157: import org.netbeans.modules.java.source.usages.ClassIndexManager;
0158: import org.netbeans.modules.java.source.usages.Index;
0159: import org.netbeans.modules.java.source.usages.Pair;
0160: import org.netbeans.modules.java.source.usages.RepositoryUpdater;
0161: import org.netbeans.modules.java.source.util.LowMemoryEvent;
0162: import org.netbeans.modules.java.source.util.LowMemoryListener;
0163: import org.netbeans.modules.java.source.util.LowMemoryNotifier;
0164: import org.netbeans.modules.java.source.usages.SymbolClassReader;
0165: import org.netbeans.spi.java.classpath.support.ClassPathSupport;
0166: import org.openide.cookies.EditorCookie;
0167: import org.openide.filesystems.FileChangeAdapter;
0168: import org.openide.filesystems.FileChangeListener;
0169: import org.openide.filesystems.FileEvent;
0170: import org.openide.filesystems.FileObject;
0171: import org.openide.filesystems.FileRenameEvent;
0172: import org.openide.filesystems.FileStateInvalidException;
0173: import org.openide.filesystems.FileUtil;
0174: import org.openide.loaders.DataObject;
0175: import org.openide.loaders.DataObjectNotFoundException;
0176: import org.openide.modules.SpecificationVersion;
0177: import org.openide.text.CloneableEditorSupport;
0178: import org.openide.util.Exceptions;
0179: import org.openide.util.Lookup;
0180: import org.openide.util.NbBundle;
0181: import org.openide.util.RequestProcessor;
0182: import org.openide.util.WeakListeners;
0183:
0184: /** Class representing Java source file opened in the editor.
0185: *
0186: * @author Petr Hrebejk, Tomas Zezula
0187: */
0188: public final class JavaSource {
0189:
0190: public static enum Phase {
0191: MODIFIED,
0192:
0193: PARSED,
0194:
0195: ELEMENTS_RESOLVED,
0196:
0197: RESOLVED,
0198:
0199: UP_TO_DATE;
0200:
0201: };
0202:
0203: public static enum Priority {
0204: MAX, HIGH, ABOVE_NORMAL, NORMAL, BELOW_NORMAL, LOW, MIN
0205: };
0206:
0207: /**
0208: * This specialization of {@link IOException} signals that a {@link JavaSource#runUserActionTask}
0209: * or {@link JavaSource#runModificationTask} failed due to lack of memory. The {@link InsufficientMemoryException#getFile}
0210: * method returns a file which cannot be processed.
0211: */
0212: public static final class InsufficientMemoryException extends
0213: IOException {
0214:
0215: private FileObject fo;
0216:
0217: private InsufficientMemoryException(final String message,
0218: final FileObject fo) {
0219: super (message);
0220: this .fo = fo;
0221: }
0222:
0223: private InsufficientMemoryException(FileObject fo) {
0224: this (NbBundle.getMessage(JavaSource.class,
0225: "MSG_UnsufficientMemoryException", FileUtil
0226: .getFileDisplayName(fo)), fo);
0227: }
0228:
0229: /**
0230: * Returns file which cannot be processed due to lack of memory.
0231: * @return {@link FileObject}
0232: */
0233: public FileObject getFile() {
0234: return this .fo;
0235: }
0236: }
0237:
0238: /**Constants for JavaSource.flags*/
0239: private static final int INVALID = 1;
0240: private static final int CHANGE_EXPECTED = INVALID << 1;
0241: private static final int RESCHEDULE_FINISHED_TASKS = CHANGE_EXPECTED << 1;
0242: private static final int UPDATE_INDEX = RESCHEDULE_FINISHED_TASKS << 1;
0243: private static final int IS_CLASS_FILE = UPDATE_INDEX << 1;
0244:
0245: private static final Pattern excludedTasks;
0246: private static final Pattern includedTasks;
0247: /**Limit for task to be marked as a slow one, in ms*/
0248: private static final int SLOW_CANCEL_LIMIT = 50;
0249: private static final PrintWriter DEV_NULL = new PrintWriter(
0250: new DevNullWriter(), false);
0251:
0252: private static final int REPARSE_DELAY = 500;
0253: private int reparseDelay;
0254:
0255: /**Used by unit tests*/
0256: static JavaFileObjectProvider jfoProvider = new DefaultJavaFileObjectProvider();
0257:
0258: /**
0259: * Helper map mapping the {@link Phase} to message for performance logger
0260: */
0261: private static Map<Phase, String> phase2Message = new HashMap<Phase, String>();
0262:
0263: private static class InternalLock {
0264: };
0265:
0266: private static final Object INTERNAL_LOCK = new InternalLock();
0267:
0268: /**
0269: * Init the maps
0270: */
0271: static {
0272: JavaSourceAccessor.setINSTANCE(new JavaSourceAccessorImpl());
0273: phase2Message.put(Phase.PARSED, "Parsed"); //NOI18N
0274: phase2Message.put(Phase.ELEMENTS_RESOLVED,
0275: "Signatures Attributed"); //NOI18N
0276: phase2Message.put(Phase.RESOLVED, "Attributed"); //NOI18N
0277:
0278: //Initialize the excludedTasks
0279: Pattern _excludedTasks = null;
0280: try {
0281: String excludedValue = System
0282: .getProperty("org.netbeans.api.java.source.JavaSource.excludedTasks"); //NOI18N
0283: if (excludedValue != null) {
0284: _excludedTasks = Pattern.compile(excludedValue);
0285: }
0286: } catch (PatternSyntaxException e) {
0287: e.printStackTrace();
0288: }
0289: excludedTasks = _excludedTasks;
0290: Pattern _includedTasks = null;
0291: try {
0292: String includedValue = System
0293: .getProperty("org.netbeans.api.java.source.JavaSource.includedTasks"); //NOI18N
0294: if (includedValue != null) {
0295: _includedTasks = Pattern.compile(includedValue);
0296: }
0297: } catch (PatternSyntaxException e) {
0298: e.printStackTrace();
0299: }
0300: includedTasks = _includedTasks;
0301:
0302: }
0303:
0304: private final static PriorityBlockingQueue<Request> requests = new PriorityBlockingQueue<Request>(
0305: 10, new RequestComparator());
0306: private final static Map<JavaSource, Collection<Request>> finishedRequests = new WeakHashMap<JavaSource, Collection<Request>>();
0307: private final static Map<JavaSource, Collection<Request>> waitingRequests = new WeakHashMap<JavaSource, Collection<Request>>();
0308: private final static Collection<CancellableTask> toRemove = new LinkedList<CancellableTask>();
0309: private final static SingleThreadFactory factory = new SingleThreadFactory();
0310: private final static CurrentRequestReference currentRequest = new CurrentRequestReference();
0311: private final static EditorRegistryListener editorRegistryListener = new EditorRegistryListener();
0312: private final static List<DeferredTask> todo = Collections
0313: .synchronizedList(new LinkedList<DeferredTask>());
0314: //Only single thread can operate on the single javac
0315: private final static ReentrantLock javacLock = new ReentrantLock(
0316: true);
0317:
0318: private final Collection<FileObject> files;
0319: final FileObject rootFo;
0320: private final FileChangeListener fileChangeListener;
0321: private DocListener listener;
0322: private DataObjectListener dataObjectListener;
0323:
0324: private final ClasspathInfo classpathInfo;
0325: private CompilationInfoImpl currentInfo;
0326: private java.util.Stack<CompilationInfoImpl> infoStack = new java.util.Stack<CompilationInfoImpl>();
0327:
0328: //Incremental parsing support
0329: private final List<Pair<DocPositionRegion, MethodTree>> positions = Collections
0330: .synchronizedList(new LinkedList<Pair<DocPositionRegion, MethodTree>>());
0331:
0332: private int flags = 0;
0333:
0334: //Preprocessor support
0335: private FilterListener filterListener;
0336:
0337: private PositionConverter binding;
0338: private final boolean supportsReparse;
0339:
0340: private static final Logger LOGGER = Logger
0341: .getLogger(JavaSource.class.getName());
0342:
0343: static {
0344: Executors.newSingleThreadExecutor(factory).submit(
0345: new CompilationJob());
0346: }
0347:
0348: /**
0349: * Returns a {@link JavaSource} instance representing given {@link org.openide.filesystems.FileObject}s
0350: * and classpath represented by given {@link ClasspathInfo}.
0351: * @param cpInfo the classpaths to be used.
0352: * @param files for which the {@link JavaSource} should be created
0353: * @return a new {@link JavaSource}
0354: * @throws {@link IllegalArgumentException} if fileObject or cpInfo is null
0355: */
0356: public static JavaSource create(final ClasspathInfo cpInfo,
0357: final Collection<? extends FileObject> files)
0358: throws IllegalArgumentException {
0359: if (files == null || cpInfo == null) {
0360: throw new IllegalArgumentException();
0361: }
0362: return create(cpInfo, null, files);
0363: }
0364:
0365: /**
0366: * Returns a {@link JavaSource} instance representing given {@link org.openide.filesystems.FileObject}s
0367: * and classpath represented by given {@link ClasspathInfo}.
0368: * @param cpInfo the classpaths to be used.
0369: * @param files for which the {@link JavaSource} should be created
0370: * @return a new {@link JavaSource}
0371: * @throws {@link IllegalArgumentException} if fileObject or cpInfo is null
0372: */
0373: public static JavaSource create(final ClasspathInfo cpInfo,
0374: final FileObject... files) throws IllegalArgumentException {
0375: if (files == null || cpInfo == null) {
0376: throw new IllegalArgumentException();
0377: }
0378: return create(cpInfo, null, Arrays.asList(files));
0379: }
0380:
0381: private static JavaSource create(final ClasspathInfo cpInfo,
0382: final PositionConverter binding,
0383: final Collection<? extends FileObject> files)
0384: throws IllegalArgumentException {
0385: try {
0386: return new JavaSource(cpInfo, binding, files);
0387: } catch (DataObjectNotFoundException donf) {
0388: Logger.getLogger("global").warning(
0389: "Ignoring non existent file: "
0390: + FileUtil.getFileDisplayName(donf
0391: .getFileObject())); //NOI18N
0392: } catch (IOException ex) {
0393: Exceptions.printStackTrace(ex);
0394: }
0395: return null;
0396: }
0397:
0398: private static Map<FileObject, Reference<JavaSource>> file2JavaSource = new WeakHashMap<FileObject, Reference<JavaSource>>();
0399:
0400: /**
0401: * Returns a {@link JavaSource} instance associated to given {@link org.openide.filesystems.FileObject},
0402: * it returns null if the {@link Document} is not associanted with data type providing the {@link JavaSource}.
0403: * @param fileObject for which the {@link JavaSource} should be found/created.
0404: * @return {@link JavaSource} or null
0405: * @throws {@link IllegalArgumentException} if fileObject is null
0406: */
0407: public static JavaSource forFileObject(FileObject fileObject)
0408: throws IllegalArgumentException {
0409: if (fileObject == null) {
0410: throw new IllegalArgumentException("fileObject == null"); //NOI18N
0411: }
0412: if (!fileObject.isValid()) {
0413: return null;
0414: }
0415:
0416: try {
0417: if (fileObject.getFileSystem().isDefault()
0418: && fileObject
0419: .getAttribute("javax.script.ScriptEngine") != null
0420: && fileObject.getAttribute("template") == Boolean.TRUE) {
0421: return null;
0422: }
0423: DataObject od = DataObject.find(fileObject);
0424:
0425: EditorCookie ec = od.getLookup().lookup(EditorCookie.class);
0426:
0427: if (!(ec instanceof CloneableEditorSupport)) {
0428: //allow creation of JavaSource for .class files:
0429: if (!("application/x-class-file".equals(FileUtil
0430: .getMIMEType(fileObject)) || "class"
0431: .equals(fileObject.getExt()))) {
0432: return null;
0433: }
0434: }
0435: } catch (FileStateInvalidException ex) {
0436: LOGGER.log(Level.FINE, null, ex);
0437: return null;
0438: } catch (DataObjectNotFoundException ex) {
0439: LOGGER.log(Level.FINE, null, ex);
0440: return null;
0441: }
0442:
0443: Reference<JavaSource> ref = file2JavaSource.get(fileObject);
0444: JavaSource js = ref != null ? ref.get() : null;
0445: if (js == null) {
0446: if ("application/x-class-file".equals(FileUtil
0447: .getMIMEType(fileObject))
0448: || "class".equals(fileObject.getExt())) { //NOI18N
0449: ClassPath bootPath = ClassPath.getClassPath(fileObject,
0450: ClassPath.BOOT);
0451: ClassPath compilePath = ClassPath.getClassPath(
0452: fileObject, ClassPath.COMPILE);
0453: if (compilePath == null) {
0454: compilePath = ClassPathSupport
0455: .createClassPath(new URL[0]);
0456: }
0457: ClassPath srcPath = ClassPath.getClassPath(fileObject,
0458: ClassPath.SOURCE);
0459: if (srcPath == null) {
0460: srcPath = ClassPathSupport
0461: .createClassPath(new URL[0]);
0462: }
0463: ClassPath execPath = ClassPath.getClassPath(fileObject,
0464: ClassPath.EXECUTE);
0465: if (execPath != null) {
0466: bootPath = ClassPathSupport.createProxyClassPath(
0467: execPath, bootPath);
0468: }
0469: final ClasspathInfo info = ClasspathInfo.create(
0470: bootPath, compilePath, srcPath);
0471: FileObject root = ClassPathSupport
0472: .createProxyClassPath(bootPath, compilePath,
0473: srcPath).findOwnerRoot(fileObject);
0474: if (root == null) {
0475: return null;
0476: }
0477: try {
0478: js = new JavaSource(info, fileObject, root);
0479: } catch (IOException ioe) {
0480: Exceptions.printStackTrace(ioe);
0481: }
0482: } else {
0483: PositionConverter binding = null;
0484: if (!"text/x-java".equals(FileUtil
0485: .getMIMEType(fileObject))
0486: && !"java".equals(fileObject.getExt())) { //NOI18N
0487: for (JavaSourceProvider provider : Lookup
0488: .getDefault().lookupAll(
0489: JavaSourceProvider.class)) {
0490: JavaFileFilterImplementation filter = provider
0491: .forFileObject(fileObject);
0492: if (filter != null) {
0493: binding = new PositionConverter(fileObject,
0494: filter);
0495: break;
0496: }
0497: }
0498: if (binding == null)
0499: return null;
0500: }
0501: js = create(ClasspathInfo.create(fileObject), binding,
0502: Collections.singletonList(fileObject));
0503: }
0504: file2JavaSource.put(fileObject,
0505: new WeakReference<JavaSource>(js));
0506: }
0507: return js;
0508: }
0509:
0510: /**
0511: * Returns a {@link JavaSource} instance associated to the given {@link javax.swing.Document},
0512: * it returns null if the {@link Document} is not
0513: * associated with data type providing the {@link JavaSource}.
0514: * @param doc {@link Document} for which the {@link JavaSource} should be found/created.
0515: * @return {@link JavaSource} or null
0516: * @throws {@link IllegalArgumentException} if doc is null
0517: */
0518: public static JavaSource forDocument(Document doc)
0519: throws IllegalArgumentException {
0520: if (doc == null) {
0521: throw new IllegalArgumentException("doc == null"); //NOI18N
0522: }
0523: Reference<?> ref = (Reference<?>) doc
0524: .getProperty(JavaSource.class);
0525: JavaSource js = ref != null ? (JavaSource) ref.get() : null;
0526: if (js == null) {
0527: Object source = doc
0528: .getProperty(Document.StreamDescriptionProperty);
0529:
0530: if (source instanceof DataObject) {
0531: DataObject dObj = (DataObject) source;
0532: if (dObj != null) {
0533: js = forFileObject(dObj.getPrimaryFile());
0534: }
0535: }
0536: }
0537: return js;
0538: }
0539:
0540: /**
0541: * Creates a new instance of JavaSource
0542: * @param files to create JavaSource for
0543: * @param cpInfo classpath info
0544: */
0545: private JavaSource(ClasspathInfo cpInfo, PositionConverter binding,
0546: Collection<? extends FileObject> files) throws IOException {
0547: this .reparseDelay = REPARSE_DELAY;
0548: this .files = Collections
0549: .unmodifiableList(new ArrayList<FileObject>(files)); //Create a defensive copy, prevent modification
0550: this .fileChangeListener = new FileChangeListenerImpl();
0551: this .binding = binding;
0552: this .supportsReparse = this .binding == null;
0553: boolean multipleSources = this .files.size() > 1, filterAssigned = false;
0554: for (Iterator<? extends FileObject> it = this .files.iterator(); it
0555: .hasNext();) {
0556: FileObject file = it.next();
0557: try {
0558: Logger.getLogger("TIMER").log(Level.FINE, "JavaSource",
0559: new Object[] { file, this });
0560: if (!multipleSources) {
0561: file.addFileChangeListener(FileUtil
0562: .weakFileChangeListener(
0563: this .fileChangeListener, file));
0564: this .assignDocumentListener(DataObject.find(file));
0565: this .dataObjectListener = new DataObjectListener(
0566: file);
0567: }
0568: if (!filterAssigned) {
0569: filterAssigned = true;
0570: if (this .binding == null) {
0571: this .binding = new PositionConverter(file,
0572: JavaFileFilterQuery.getFilter(file));
0573: }
0574: JavaFileFilterImplementation filter = this .binding
0575: .getFilter();
0576: if (filter != null) {
0577: this .filterListener = new FilterListener(filter);
0578: }
0579: }
0580: } catch (DataObjectNotFoundException donf) {
0581: if (multipleSources) {
0582: LOGGER.warning("Ignoring non existent file: "
0583: + FileUtil.getFileDisplayName(file)); //NOI18N
0584: it.remove();
0585: } else {
0586: throw donf;
0587: }
0588: }
0589: }
0590: this .classpathInfo = cpInfo;
0591: if (this .files.size() == 1) {
0592: this .rootFo = classpathInfo.getClassPath(PathKind.SOURCE)
0593: .findOwnerRoot(this .files.iterator().next());
0594: } else {
0595: this .rootFo = null;
0596: }
0597: this .classpathInfo.addChangeListener(WeakListeners.change(
0598: this .listener, this .classpathInfo));
0599: }
0600:
0601: private JavaSource(final ClasspathInfo info,
0602: final FileObject classFileObject, final FileObject root)
0603: throws IOException {
0604: assert info != null;
0605: assert classFileObject != null;
0606: assert root != null;
0607: this .reparseDelay = REPARSE_DELAY;
0608: this .files = Collections
0609: .<FileObject> singletonList(classFileObject);
0610: this .fileChangeListener = new FileChangeListenerImpl();
0611: classFileObject.addFileChangeListener(FileUtil
0612: .weakFileChangeListener(this .fileChangeListener,
0613: classFileObject));
0614: this .dataObjectListener = new DataObjectListener(
0615: classFileObject);
0616: this .classpathInfo = info;
0617: this .rootFo = root;
0618: this .classpathInfo.addChangeListener(WeakListeners.change(
0619: this .listener, this .classpathInfo));
0620: this .flags |= IS_CLASS_FILE;
0621: this .supportsReparse = false;
0622: this .binding = new PositionConverter(classFileObject, null);
0623: }
0624:
0625: private static final Set<StackTraceElement> warnedAboutRunInEQ = new HashSet<StackTraceElement>();
0626:
0627: /** Runs a task which permits for controlling phases of the parsing process.
0628: * You probably do not want to call this method unless you are reacting to
0629: * some user's GUI input which requires immediate action (e.g. code completion popup).
0630: * In all other cases use {@link JavaSourceTaskFactory}.<BR>
0631: * Call to this method will cancel processing of all the phase completion tasks until
0632: * this task does not finish.<BR>
0633: * @see org.netbeans.api.java.source.CancellableTask for information about implementation requirements
0634: * @param task The task which.
0635: * @param shared if true the java compiler may be reused by other {@link org.netbeans.api.java.source.CancellableTasks},
0636: * the value false may have negative impact on the IDE performance.
0637: * <div class="nonnormative">
0638: * <p>
0639: * It's legal to nest the {@link JavaSource#runUserActionTask} into another {@link JavaSource#runUserActionTask}.
0640: * It's also legal to nest the {@link JavaSource#runModificationTask} into {@link JavaSource#runUserActionTask},
0641: * the outer {@link JavaSource#runUserActionTask} does not see changes caused by nested {@link JavaSource#runModificationTask},
0642: * but the following nested task see them.
0643: * </p>
0644: * </div>
0645: */
0646: public void runUserActionTask(
0647: final Task<CompilationController> task, final boolean shared)
0648: throws IOException {
0649: if (task == null) {
0650: throw new IllegalArgumentException("Task cannot be null"); //NOI18N
0651: }
0652:
0653: assert javacLock.isHeldByCurrentThread()
0654: || !holdsDocumentWriteLock(files) : "JavaSource.runCompileControlTask called under Document write lock."; //NOI18N
0655:
0656: boolean a = false;
0657: assert a = true;
0658: if (a && javax.swing.SwingUtilities.isEventDispatchThread()) {
0659: StackTraceElement stackTraceElement = findCaller(Thread
0660: .currentThread().getStackTrace());
0661: if (stackTraceElement != null
0662: && warnedAboutRunInEQ.add(stackTraceElement)) {
0663: LOGGER
0664: .warning("JavaSource.runUserActionTask called in AWT event thread by: "
0665: + stackTraceElement); // NOI18N
0666: }
0667: }
0668:
0669: if (this .files.size() <= 1) {
0670: final JavaSource.Request request = currentRequest
0671: .getTaskToCancel();
0672: try {
0673: if (request != null) {
0674: request.task.cancel();
0675: }
0676: this .javacLock.lock();
0677: try {
0678: CompilationInfoImpl currentInfo = null;
0679: boolean jsInvalid;
0680: Pair<DocPositionRegion, MethodTree> changedMethod = null;
0681: synchronized (this ) {
0682: jsInvalid = this .currentInfo == null
0683: || (this .flags & INVALID) != 0;
0684: currentInfo = this .currentInfo;
0685: changedMethod = (currentInfo == null ? null
0686: : this .currentInfo.getChangedTree());
0687: if (!shared) {
0688: this .flags |= INVALID;
0689: }
0690: }
0691: if (jsInvalid) {
0692: boolean needsFullReparse = true;
0693: if (changedMethod != null
0694: && currentInfo != null) {
0695: needsFullReparse = !reparseMethod(
0696: currentInfo, changedMethod.second,
0697: changedMethod.first.getText());
0698: }
0699: if (needsFullReparse) {
0700: currentInfo = createCurrentInfo(this ,
0701: binding, null);
0702: }
0703: if (shared) {
0704: synchronized (this ) {
0705: if (this .currentInfo == null
0706: || (this .flags & INVALID) != 0) {
0707: this .currentInfo = currentInfo;
0708: this .flags &= ~INVALID;
0709: } else {
0710: currentInfo = this .currentInfo;
0711: }
0712: }
0713: }
0714: }
0715: assert currentInfo != null;
0716: if (shared) {
0717: if (!infoStack.isEmpty()) {
0718: currentInfo = infoStack.peek();
0719: }
0720: } else {
0721: infoStack.push(currentInfo);
0722: }
0723: try {
0724: final CompilationController clientController = new CompilationController(
0725: currentInfo);
0726: try {
0727: task.run(clientController);
0728: } finally {
0729: if (shared) {
0730: clientController.invalidate();
0731: }
0732: }
0733: } finally {
0734: if (!shared) {
0735: infoStack.pop();
0736: }
0737: }
0738: } catch (CompletionFailure e) {
0739: IOException ioe = new IOException();
0740: ioe.initCause(e);
0741: throw ioe;
0742: } catch (RuntimeException e) {
0743: throw e;
0744: } catch (Exception e) {
0745: IOException ioe = new IOException();
0746: ioe.initCause(e);
0747: throw ioe;
0748: } finally {
0749: this .javacLock.unlock();
0750: }
0751: } finally {
0752: currentRequest.cancelCompleted(request);
0753: }
0754: } else {
0755: final JavaSource.Request request = currentRequest
0756: .getTaskToCancel();
0757: try {
0758: if (request != null) {
0759: request.task.cancel();
0760: }
0761: this .javacLock.lock();
0762: try {
0763: JavacTaskImpl jt = null;
0764: FileObject activeFile = null;
0765: Iterator<FileObject> files = this .files.iterator();
0766: while (files.hasNext() || activeFile != null) {
0767: boolean restarted;
0768: if (activeFile == null) {
0769: activeFile = files.next();
0770: restarted = false;
0771: } else {
0772: restarted = true;
0773: }
0774: CompilationInfoImpl ci = createCurrentInfo(
0775: this , new PositionConverter(activeFile,
0776: null), jt);
0777: CompilationController clientController = new CompilationController(
0778: ci);
0779: try {
0780: task.run(clientController);
0781: } finally {
0782: if (shared) {
0783: clientController.invalidate();
0784: }
0785: }
0786: if (!ci.needsRestart) {
0787: jt = ci.getJavacTask();
0788: Log.instance(jt.getContext()).nerrors = 0;
0789: activeFile = null;
0790: } else {
0791: jt = null;
0792: ci = null;
0793: System.gc();
0794: if (restarted) {
0795: throw new InsufficientMemoryException(
0796: activeFile);
0797: }
0798: }
0799: }
0800: } catch (CompletionFailure e) {
0801: IOException ioe = new IOException();
0802: ioe.initCause(e);
0803: throw ioe;
0804: } catch (RuntimeException e) {
0805: throw e;
0806: } catch (Exception e) {
0807: IOException ioe = new IOException();
0808: ioe.initCause(e);
0809: throw ioe;
0810: } finally {
0811: this .javacLock.unlock();
0812: }
0813: } finally {
0814: currentRequest.cancelCompleted(request);
0815: }
0816: }
0817: }
0818:
0819: private void runUserActionTask(
0820: final CancellableTask<CompilationController> task,
0821: final boolean shared) throws IOException {
0822: final Task<CompilationController> _task = task;
0823: this .runUserActionTask(_task, shared);
0824: }
0825:
0826: /**
0827: * Performs the given task when the scan finished. When no background scan is running
0828: * it performs the given task synchronously. When the background scan is active it queues
0829: * the given task and returns, the task is performed when the background scan completes by
0830: * the thread doing the background scan.
0831: * @param task to be performed
0832: * @param shared if true the java compiler may be reused by other {@link org.netbeans.api.java.source.CancellableTasks},
0833: * the value false may have negative impact on the IDE performance.
0834: * @return {@link Future} which can be used to find out the sate of the task {@link Future#isDone} or {@link Future#isCancelled}.
0835: * The caller may cancel the task using {@link Future#cancel} or wait until the task is performed {@link Future#get}.
0836: * @throws IOException encapsulating the exception thrown by {@link CancellableTasks#run}
0837: * @since 0.12
0838: */
0839: public Future<Void> runWhenScanFinished(
0840: final Task<CompilationController> task, final boolean shared)
0841: throws IOException {
0842: assert task != null;
0843: final ScanSync sync = new ScanSync(task);
0844: final DeferredTask r = new DeferredTask(this , task, shared,
0845: sync);
0846: //0) Add speculatively task to be performed at the end of background scan
0847: todo.add(r);
0848: if (RepositoryUpdater.getDefault().isScanInProgress()) {
0849: return sync;
0850: }
0851: //1) Try to aquire javac lock, if successfull no task is running
0852: // perform the given taks synchronously if it wasn't already performed
0853: // by background scan.
0854: final boolean locked = javacLock.tryLock();
0855: if (locked) {
0856: try {
0857: if (todo.remove(r)) {
0858: try {
0859: runUserActionTask(task, shared);
0860: } finally {
0861: sync.taskFinished();
0862: }
0863: }
0864: } finally {
0865: javacLock.unlock();
0866: }
0867: } else {
0868: //Otherwise interrupt currently running task and try to aquire lock
0869: do {
0870: final JavaSource.Request[] request = new JavaSource.Request[1];
0871: boolean isScanner = currentRequest
0872: .getUserTaskToCancel(request);
0873: try {
0874: if (isScanner) {
0875: return sync;
0876: }
0877: if (request[0] != null) {
0878: request[0].task.cancel();
0879: }
0880: if (javacLock.tryLock(100, TimeUnit.MILLISECONDS)) {
0881: try {
0882: if (todo.remove(r)) {
0883: try {
0884: runUserActionTask(task, shared);
0885: return sync;
0886: } finally {
0887: sync.taskFinished();
0888: }
0889: } else {
0890: return sync;
0891: }
0892: } finally {
0893: javacLock.unlock();
0894: }
0895: }
0896: } catch (InterruptedException e) {
0897: throw (InterruptedIOException) new InterruptedIOException()
0898: .initCause(e);
0899: } finally {
0900: if (!isScanner) {
0901: currentRequest.cancelCompleted(request[0]);
0902: }
0903: }
0904: } while (true);
0905: }
0906: return sync;
0907: }
0908:
0909: private Future<Void> runWhenScanFinished(
0910: final CancellableTask<CompilationController> task,
0911: final boolean shared) throws IOException {
0912: final Task<CompilationController> _task = task;
0913: return this .runWhenScanFinished(_task, shared);
0914: }
0915:
0916: /** Runs a task which permits for modifying the sources.
0917: * Call to this method will cancel processing of all the phase completion tasks until
0918: * this task does not finish.<BR>
0919: * @see Task for information about implementation requirements
0920: * @param task The task which.
0921: */
0922: public ModificationResult runModificationTask(Task<WorkingCopy> task)
0923: throws IOException {
0924: if (task == null) {
0925: throw new IllegalArgumentException("Task cannot be null"); //NOI18N
0926: }
0927:
0928: assert javacLock.isHeldByCurrentThread()
0929: || !holdsDocumentWriteLock(files) : "JavaSource.runModificationTask called under Document write lock."; //NOI18N
0930:
0931: boolean a = false;
0932: assert a = true;
0933: if (a && javax.swing.SwingUtilities.isEventDispatchThread()) {
0934: StackTraceElement stackTraceElement = findCaller(Thread
0935: .currentThread().getStackTrace());
0936: if (stackTraceElement != null
0937: && warnedAboutRunInEQ.add(stackTraceElement)) {
0938: LOGGER
0939: .warning("JavaSource.runModificationTask called in AWT event thread by: "
0940: + stackTraceElement); //NOI18N
0941: }
0942: }
0943:
0944: ModificationResult result = new ModificationResult(this );
0945: if (this .files.size() <= 1) {
0946: long start = System.currentTimeMillis();
0947: final JavaSource.Request request = currentRequest
0948: .getTaskToCancel();
0949: try {
0950: if (request != null) {
0951: request.task.cancel();
0952: }
0953: this .javacLock.lock();
0954: try {
0955: CompilationInfoImpl currentInfo = null;
0956: boolean jsInvalid;
0957: Pair<DocPositionRegion, MethodTree> changedMethod;
0958: synchronized (this ) {
0959: jsInvalid = this .currentInfo == null
0960: || (this .flags & INVALID) != 0;
0961: currentInfo = this .currentInfo;
0962: changedMethod = currentInfo == null ? null
0963: : currentInfo.getChangedTree();
0964: }
0965: if (jsInvalid) {
0966: boolean needsFullReparse = true;
0967: if (changedMethod != null
0968: && currentInfo != null) {
0969: needsFullReparse = !reparseMethod(
0970: currentInfo, changedMethod.second,
0971: changedMethod.first.getText());
0972: }
0973: if (needsFullReparse) {
0974: currentInfo = createCurrentInfo(this ,
0975: binding, null);
0976: }
0977: synchronized (this ) {
0978: if (this .currentInfo == null
0979: || (this .flags & INVALID) != 0) {
0980: this .currentInfo = currentInfo;
0981: this .flags &= ~INVALID;
0982: } else {
0983: currentInfo = this .currentInfo;
0984: }
0985: }
0986: }
0987: assert currentInfo != null;
0988: WorkingCopy copy = new WorkingCopy(currentInfo);
0989: task.run(copy);
0990: List<Difference> diffs = copy.getChanges();
0991: if (diffs != null && diffs.size() > 0)
0992: result.diffs.put(currentInfo.getFileObject(),
0993: diffs);
0994: } catch (CompletionFailure e) {
0995: IOException ioe = new IOException();
0996: ioe.initCause(e);
0997: throw ioe;
0998: } catch (RuntimeException e) {
0999: throw e;
1000: } catch (Exception e) {
1001: IOException ioe = new IOException();
1002: ioe.initCause(e);
1003: throw ioe;
1004: } finally {
1005: this .javacLock.unlock();
1006: }
1007: } finally {
1008: currentRequest.cancelCompleted(request);
1009: }
1010: Logger.getLogger("TIMER").log(
1011: Level.FINE,
1012: "Modification Task",
1013: new Object[] { currentInfo.getFileObject(),
1014: System.currentTimeMillis() - start });
1015: } else {
1016: final JavaSource.Request request = currentRequest
1017: .getTaskToCancel();
1018: try {
1019: if (request != null) {
1020: request.task.cancel();
1021: }
1022: this .javacLock.lock();
1023: try {
1024: JavacTaskImpl jt = null;
1025: FileObject activeFile = null;
1026: Iterator<FileObject> files = this .files.iterator();
1027: while (files.hasNext() || activeFile != null) {
1028: boolean restarted;
1029: if (activeFile == null) {
1030: activeFile = files.next();
1031: restarted = false;
1032: } else {
1033: restarted = true;
1034: }
1035: CompilationInfoImpl ci = createCurrentInfo(
1036: this , new PositionConverter(activeFile,
1037: null), jt);
1038: WorkingCopy copy = new WorkingCopy(ci);
1039: task.run(copy);
1040: if (!ci.needsRestart) {
1041: jt = ci.getJavacTask();
1042: Log.instance(jt.getContext()).nerrors = 0;
1043: List<Difference> diffs = copy.getChanges();
1044: if (diffs != null && diffs.size() > 0)
1045: result.diffs.put(ci.getFileObject(),
1046: diffs);
1047: activeFile = null;
1048: } else {
1049: jt = null;
1050: ci = null;
1051: System.gc();
1052: if (restarted) {
1053: throw new InsufficientMemoryException(
1054: activeFile);
1055: }
1056: }
1057: }
1058: } catch (CompletionFailure e) {
1059: IOException ioe = new IOException();
1060: ioe.initCause(e);
1061: throw ioe;
1062: } catch (RuntimeException e) {
1063: throw e;
1064: } catch (Exception e) {
1065: IOException ioe = new IOException();
1066: ioe.initCause(e);
1067: throw ioe;
1068: } finally {
1069: this .javacLock.unlock();
1070: }
1071: } finally {
1072: currentRequest.cancelCompleted(request);
1073: }
1074: }
1075: synchronized (this ) {
1076: this .flags |= INVALID;
1077: }
1078: return result;
1079: }
1080:
1081: private ModificationResult runModificationTask(
1082: CancellableTask<WorkingCopy> task) throws IOException {
1083: final Task<WorkingCopy> _task = task;
1084: return this .runModificationTask(_task);
1085: }
1086:
1087: /** Adds a task to given compilation phase. The tasks will run sequentially by
1088: * priority after given phase is reached.
1089: * @see CancellableTask for information about implementation requirements
1090: * @task The task to run.
1091: * @phase In which phase should the task run
1092: * @priority Priority of the task.
1093: */
1094: void addPhaseCompletionTask(CancellableTask<CompilationInfo> task,
1095: Phase phase, Priority priority) throws IOException {
1096: if (task == null) {
1097: throw new IllegalArgumentException("Task cannot be null"); //NOI18N
1098: }
1099: if (phase == null || phase == Phase.MODIFIED) {
1100: throw new IllegalArgumentException(String.format(
1101: "The %s is not a legal value of phase", phase)); //NOI18N
1102: }
1103: if (priority == null) {
1104: throw new IllegalArgumentException(
1105: "The priority cannot be null"); //NOI18N
1106: }
1107: final String taskClassName = task.getClass().getName();
1108: if (excludedTasks != null
1109: && excludedTasks.matcher(taskClassName).matches()) {
1110: if (includedTasks == null
1111: || !includedTasks.matcher(taskClassName).matches())
1112: return;
1113: }
1114: handleAddRequest(new Request(task, this , phase, priority, true));
1115: }
1116:
1117: /** Removes the task from the phase queue.
1118: * @task The task to remove.
1119: */
1120: void removePhaseCompletionTask(CancellableTask<CompilationInfo> task) {
1121: final String taskClassName = task.getClass().getName();
1122: if (excludedTasks != null
1123: && excludedTasks.matcher(taskClassName).matches()) {
1124: if (includedTasks == null
1125: || !includedTasks.matcher(taskClassName).matches()) {
1126: return;
1127: }
1128: }
1129: synchronized (INTERNAL_LOCK) {
1130: toRemove.add(task);
1131: Collection<Request> rqs = finishedRequests.get(this );
1132: if (rqs != null) {
1133: for (Iterator<Request> it = rqs.iterator(); it
1134: .hasNext();) {
1135: Request rq = it.next();
1136: if (rq.task == task) {
1137: it.remove();
1138: }
1139: }
1140: }
1141: }
1142: }
1143:
1144: /**Rerun the task in case it was already run. Does nothing if the task was not already run.
1145: *
1146: * @task to reschedule
1147: */
1148: void rescheduleTask(CancellableTask<CompilationInfo> task) {
1149: synchronized (INTERNAL_LOCK) {
1150: JavaSource.Request request = this .currentRequest
1151: .getTaskToCancel(task);
1152: if (request == null) {
1153: out: for (Iterator<Collection<Request>> it = finishedRequests
1154: .values().iterator(); it.hasNext();) {
1155: Collection<Request> cr = it.next();
1156: for (Iterator<Request> it2 = cr.iterator(); it2
1157: .hasNext();) {
1158: Request fr = it2.next();
1159: if (task == fr.task) {
1160: it2.remove();
1161: JavaSource.requests.add(fr);
1162: if (cr.size() == 0) {
1163: it.remove();
1164: }
1165: break out;
1166: }
1167: }
1168: }
1169: } else {
1170: currentRequest.cancelCompleted(request);
1171: }
1172: }
1173: }
1174:
1175: /**
1176: * Marks this {@link JavaSource} as modified, causes that the cached information are
1177: * cleared and all the PhaseCompletionTasks are restarted.
1178: * The only client of this method should be the JavaDataObject or other DataObjects
1179: * providing the {@link JavaSource}. If you call this method in another case you are
1180: * probably doing something incorrect.
1181: */
1182: void revalidate() {
1183: this .resetState(true, false);
1184: }
1185:
1186: /**
1187: * Returns the classpaths ({@link ClasspathInfo}) used by this
1188: * {@link JavaSource}
1189: * @return {@link ClasspathInfo}, never returns null.
1190: */
1191: public ClasspathInfo getClasspathInfo() {
1192: return classpathInfo;
1193: }
1194:
1195: /**
1196: * Returns unmodifiable {@link Collection} of {@link FileObject}s used by this {@link JavaSource}
1197: * @return the {@link FileObject}s
1198: */
1199: public Collection<FileObject> getFileObjects() {
1200: return files;
1201: }
1202:
1203: JavacTaskImpl createJavacTask(
1204: final DiagnosticListener<? super JavaFileObject> diagnosticListener,
1205: ClassNamesForFileOraculum oraculum) {
1206: String sourceLevel = null;
1207: if (!this .files.isEmpty()) {
1208: if (LOGGER.isLoggable(Level.FINER)) {
1209: LOGGER
1210: .finer("Created new JavacTask for: "
1211: + this .files);
1212: }
1213: FileObject file = files.iterator().next();
1214:
1215: sourceLevel = SourceLevelQuery.getSourceLevel(file);
1216:
1217: FileObject root = getClasspathInfo().getClassPath(
1218: PathKind.SOURCE).findOwnerRoot(file);
1219:
1220: if (root != null && sourceLevel != null) {
1221: try {
1222: RepositoryUpdater.getDefault().verifySourceLevel(
1223: root.getURL(), sourceLevel);
1224: } catch (IOException ex) {
1225: LOGGER.log(Level.FINE, null, ex);
1226: }
1227: }
1228: }
1229: if (sourceLevel == null) {
1230: sourceLevel = JavaPlatformManager.getDefault()
1231: .getDefaultPlatform().getSpecification()
1232: .getVersion().toString();
1233: }
1234: JavacTaskImpl javacTask = createJavacTask(getClasspathInfo(),
1235: diagnosticListener, sourceLevel, false, oraculum);
1236: Context context = javacTask.getContext();
1237: JSCancelService.preRegister(context);
1238: JSFlowListener.preRegister(context);
1239: TreeLoader.preRegister(context, getClasspathInfo());
1240: Messager.preRegister(context, null, DEV_NULL, DEV_NULL,
1241: DEV_NULL);
1242: ErrorHandlingJavadocEnter.preRegister(context);
1243: JavadocMemberEnter.preRegister(context);
1244: JavadocEnv.preRegister(context, getClasspathInfo());
1245: DocCommentScanner.Factory.preRegister(context);
1246: com.sun.tools.javac.main.JavaCompiler.instance(context).keepComments = true;
1247: return javacTask;
1248: }
1249:
1250: private static JavacTaskImpl createJavacTask(
1251: final ClasspathInfo cpInfo,
1252: final DiagnosticListener<? super JavaFileObject> diagnosticListener,
1253: final String sourceLevel,
1254: final boolean backgroundCompilation,
1255: ClassNamesForFileOraculum cnih) {
1256: ArrayList<String> options = new ArrayList<String>();
1257: String lintOptions = CompilerSettings.getCommandLine();
1258:
1259: if (lintOptions.length() > 0) {
1260: options.addAll(Arrays.asList(lintOptions.split(" ")));
1261: }
1262: if (!backgroundCompilation) {
1263: options.add("-Xjcov"); //NOI18N, Make the compiler store end positions
1264: options.add("-XDdisableStringFolding"); //NOI18N
1265: } else {
1266: options.add("-XDbackgroundCompilation"); //NOI18N
1267: options.add("-XDcompilePolicy=byfile"); //NOI18N
1268: }
1269: options.add("-XDide"); // NOI18N, javac runs inside the IDE
1270: options.add("-g:"); // NOI18N, Enable some debug info
1271: options.add("-g:lines"); // NOI18N, Make the compiler to maintain line table
1272: options.add("-g:vars"); // NOI18N, Make the compiler to maintain local variables table
1273: options.add("-source"); // NOI18N
1274: options.add(validateSourceLevel(sourceLevel));
1275:
1276: ClassLoader orig = Thread.currentThread()
1277: .getContextClassLoader();
1278: try {
1279: //The ToolProvider.defaultJavaCompiler will use the context classloader to load the javac implementation
1280: //it should be load by the current module's classloader (should delegate to other module's classloaders as necessary)
1281: Thread.currentThread().setContextClassLoader(
1282: ClasspathInfo.class.getClassLoader());
1283: JavaCompiler tool = ToolProvider.getSystemJavaCompiler();
1284: JavacTaskImpl task = (JavacTaskImpl) tool.getTask(null,
1285: cpInfo.getFileManager(), diagnosticListener,
1286: options, null, Collections
1287: .<JavaFileObject> emptySet());
1288: Context context = task.getContext();
1289:
1290: if (backgroundCompilation) {
1291: SymbolClassReader.preRegister(context, false);
1292: } else {
1293: SymbolClassReader.preRegister(context, true);
1294: }
1295:
1296: if (cnih != null) {
1297: context.put(ClassNamesForFileOraculum.class, cnih);
1298: }
1299: return task;
1300: } finally {
1301: Thread.currentThread().setContextClassLoader(orig);
1302: }
1303: }
1304:
1305: private static class ErrorHandlingJavadocEnter extends JavadocEnter {
1306:
1307: private Messager messager;
1308:
1309: public static void preRegister(final Context context) {
1310: context.put(enterKey, new Context.Factory<Enter>() {
1311: public Enter make() {
1312: return new ErrorHandlingJavadocEnter(context);
1313: }
1314: });
1315: }
1316:
1317: protected ErrorHandlingJavadocEnter(Context context) {
1318: super (context);
1319: messager = Messager.instance0(context);
1320: }
1321:
1322: public @Override
1323: void main(com.sun.tools.javac.util.List<JCCompilationUnit> trees) {
1324: //Todo: Check everytime after the java update that JavaDocEnter.main or Enter.main
1325: //are not changed.
1326: this .complete(trees, null);
1327: }
1328: }
1329:
1330: /**
1331: * Not synchronized, only the CompilationJob's thread can call it!!!!
1332: *
1333: */
1334: static Phase moveToPhase(final Phase phase,
1335: final CompilationInfoImpl currentInfo,
1336: final boolean cancellable) throws IOException {
1337: Phase parserError = currentInfo.parserCrashed;
1338: assert parserError != null;
1339: Phase currentPhase = currentInfo.getPhase();
1340: final boolean isMultiFiles = currentInfo.getJavaSource().files
1341: .size() > 1;
1342: LowMemoryNotifier lm = null;
1343: LMListener lmListener = null;
1344: if (isMultiFiles) {
1345: lm = LowMemoryNotifier.getDefault();
1346: assert lm != null;
1347: lmListener = new LMListener();
1348: lm.addLowMemoryListener(lmListener);
1349: }
1350: try {
1351: if (lmListener != null
1352: && lmListener.lowMemory.getAndSet(false)) {
1353: currentInfo.needsRestart = true;
1354: return currentPhase;
1355: }
1356: if (currentPhase.compareTo(Phase.PARSED) < 0
1357: && phase.compareTo(Phase.PARSED) >= 0
1358: && phase.compareTo(parserError) <= 0) {
1359: if (cancellable && currentRequest.isCanceled()) {
1360: //Keep the currentPhase unchanged, it may happen that an userActionTask
1361: //runnig after the phace completion task may still use it.
1362: return Phase.MODIFIED;
1363: }
1364: long start = System.currentTimeMillis();
1365: // XXX - this might be with wrong encoding
1366: Iterable<? extends CompilationUnitTree> trees = currentInfo
1367: .getJavacTask()
1368: .parse(new JavaFileObject[] { currentInfo.jfo });
1369: assert trees != null : "Did not parse anything"; //NOI18N
1370: Iterator<? extends CompilationUnitTree> it = trees
1371: .iterator();
1372: assert it.hasNext();
1373: CompilationUnitTree unit = it.next();
1374: currentInfo.setCompilationUnit(unit);
1375: assert !it.hasNext();
1376: final Document doc = currentInfo.javaSource.listener == null ? null
1377: : currentInfo.javaSource.listener.document;
1378: if (doc != null
1379: && currentInfo.javaSource.supportsReparse) {
1380: FindMethodRegionsVisitor v = new FindMethodRegionsVisitor(
1381: doc, Trees.instance(
1382: currentInfo.getJavacTask())
1383: .getSourcePositions());
1384: v.visit(unit, null);
1385: synchronized (currentInfo.javaSource.positions) {
1386: currentInfo.javaSource.positions.clear();
1387: currentInfo.javaSource.positions
1388: .addAll(v.posRegions);
1389: }
1390: }
1391: currentPhase = Phase.PARSED;
1392: long end = System.currentTimeMillis();
1393: FileObject file = currentInfo.getFileObject();
1394: Logger.getLogger("TIMER")
1395: .log(Level.FINE, "Compilation Unit",
1396: new Object[] { file, unit });
1397:
1398: logTime(file, currentPhase, (end - start));
1399: }
1400: if (lmListener != null
1401: && lmListener.lowMemory.getAndSet(false)) {
1402: currentInfo.needsRestart = true;
1403: return currentPhase;
1404: }
1405: if (currentPhase == Phase.PARSED
1406: && phase.compareTo(Phase.ELEMENTS_RESOLVED) >= 0
1407: && phase.compareTo(parserError) <= 0) {
1408: if (cancellable && currentRequest.isCanceled()) {
1409: return Phase.MODIFIED;
1410: }
1411: long start = System.currentTimeMillis();
1412: currentInfo.getJavacTask().enter();
1413: currentPhase = Phase.ELEMENTS_RESOLVED;
1414: long end = System.currentTimeMillis();
1415: logTime(currentInfo.getFileObject(), currentPhase,
1416: (end - start));
1417: }
1418: if (lmListener != null
1419: && lmListener.lowMemory.getAndSet(false)) {
1420: currentInfo.needsRestart = true;
1421: return currentPhase;
1422: }
1423: if (currentPhase == Phase.ELEMENTS_RESOLVED
1424: && phase.compareTo(Phase.RESOLVED) >= 0
1425: && phase.compareTo(parserError) <= 0) {
1426: if (cancellable && currentRequest.isCanceled()) {
1427: return Phase.MODIFIED;
1428: }
1429: long start = System.currentTimeMillis();
1430: currentInfo.getJavacTask().analyze();
1431: currentPhase = Phase.RESOLVED;
1432: long end = System.currentTimeMillis();
1433: logTime(currentInfo.getFileObject(), currentPhase,
1434: (end - start));
1435: }
1436: if (lmListener != null
1437: && lmListener.lowMemory.getAndSet(false)) {
1438: currentInfo.needsRestart = true;
1439: return currentPhase;
1440: }
1441: if (currentPhase == Phase.RESOLVED
1442: && phase.compareTo(Phase.UP_TO_DATE) >= 0) {
1443: currentPhase = Phase.UP_TO_DATE;
1444: }
1445: } catch (CouplingAbort a) {
1446: RepositoryUpdater.couplingAbort(a, currentInfo.jfo);
1447: currentInfo.needsRestart = true;
1448: return currentPhase;
1449: } catch (CancelAbort ca) {
1450: currentPhase = Phase.MODIFIED;
1451: } catch (Abort abort) {
1452: parserError = currentPhase;
1453: } catch (IOException ex) {
1454: currentInfo.parserCrashed = currentPhase;
1455: dumpSource(currentInfo, ex);
1456: throw ex;
1457: } catch (RuntimeException ex) {
1458: parserError = currentPhase;
1459: dumpSource(currentInfo, ex);
1460: throw ex;
1461: } catch (Error ex) {
1462: parserError = currentPhase;
1463: dumpSource(currentInfo, ex);
1464: throw ex;
1465: }
1466:
1467: finally {
1468: if (isMultiFiles) {
1469: assert lm != null;
1470: assert lmListener != null;
1471: lm.removeLowMemoryListener(lmListener);
1472: }
1473: currentInfo.setPhase(currentPhase);
1474: currentInfo.parserCrashed = parserError;
1475: }
1476: return currentPhase;
1477: }
1478:
1479: static void logTime(FileObject source, Phase phase, long time) {
1480: assert source != null && phase != null;
1481: String message = phase2Message.get(phase);
1482: assert message != null;
1483: Logger.getLogger("TIMER").log(Level.FINE, message,
1484: new Object[] { source, time });
1485: }
1486:
1487: boolean isClassFile() {
1488: return (this .flags & IS_CLASS_FILE) != 0;
1489: }
1490:
1491: private static final RequestProcessor RP = new RequestProcessor(
1492: "JavaSource-event-collector", 1); //NOI18N
1493:
1494: private final RequestProcessor.Task resetTask = RP
1495: .create(new Runnable() {
1496: public void run() {
1497: resetStateImpl();
1498: }
1499: });
1500:
1501: private void resetState(boolean invalidate, boolean updateIndex) {
1502: resetState(invalidate, updateIndex, null);
1503: }
1504:
1505: private void resetState(boolean invalidate, boolean updateIndex,
1506: Pair<DocPositionRegion, MethodTree> changedMethod) {
1507: boolean invalid;
1508: synchronized (this ) {
1509: invalid = (this .flags & INVALID) != 0;
1510: this .flags |= CHANGE_EXPECTED;
1511: if (invalidate) {
1512: this .flags |= (INVALID | RESCHEDULE_FINISHED_TASKS);
1513: if (this .currentInfo != null) {
1514: this .currentInfo.setChangedMethod(changedMethod);
1515: }
1516: }
1517: if (updateIndex) {
1518: this .flags |= UPDATE_INDEX;
1519: }
1520: }
1521: if (updateIndex && !invalid) {
1522: //First change set the index as dirty
1523: updateIndex();
1524: }
1525: Request r = currentRequest.getTaskToCancel(invalidate);
1526: if (r != null) {
1527: r.task.cancel();
1528: Request oldR = rst.getAndSet(r);
1529: assert oldR == null;
1530: }
1531: if (!k24) {
1532: resetTask.schedule(reparseDelay);
1533: }
1534: }
1535:
1536: private final AtomicReference<Request> rst = new AtomicReference<JavaSource.Request>();
1537: private volatile boolean k24;
1538:
1539: /**
1540: * Not synchronized, only sets the atomic state and clears the listeners
1541: *
1542: */
1543: private void resetStateImpl() {
1544: if (!k24) {
1545: Request r = rst.getAndSet(null);
1546: currentRequest.cancelCompleted(r);
1547: synchronized (INTERNAL_LOCK) {
1548: boolean reschedule, updateIndex;
1549: synchronized (this ) {
1550: reschedule = (this .flags & RESCHEDULE_FINISHED_TASKS) != 0;
1551: updateIndex = (this .flags & UPDATE_INDEX) != 0;
1552: this .flags &= ~(RESCHEDULE_FINISHED_TASKS
1553: | CHANGE_EXPECTED | UPDATE_INDEX);
1554: }
1555: if (updateIndex) {
1556: //Last change set the index as dirty
1557: updateIndex();
1558: }
1559: Collection<Request> cr;
1560: if (reschedule) {
1561: if ((cr = JavaSource.finishedRequests.remove(this )) != null
1562: && cr.size() > 0) {
1563: JavaSource.requests.addAll(cr);
1564: }
1565: }
1566: if ((cr = JavaSource.waitingRequests.remove(this )) != null
1567: && cr.size() > 0) {
1568: JavaSource.requests.addAll(cr);
1569: }
1570: }
1571: }
1572: }
1573:
1574: private void updateIndex() {
1575: if (this .rootFo != null) {
1576: try {
1577: ClassIndexImpl ciImpl = ClassIndexManager.getDefault()
1578: .getUsagesQuery(this .rootFo.getURL());
1579: if (ciImpl != null) {
1580: ciImpl.setDirty(this );
1581: }
1582: } catch (IOException ioe) {
1583: Exceptions.printStackTrace(ioe);
1584: }
1585: }
1586: }
1587:
1588: private void assignDocumentListener(final DataObject od)
1589: throws IOException {
1590: EditorCookie.Observable ec = od
1591: .getCookie(EditorCookie.Observable.class);
1592: if (ec != null) {
1593: this .listener = new DocListener(ec);
1594: } else {
1595: LOGGER.log(Level.WARNING, String.format(
1596: "File: %s has no EditorCookie.Observable", FileUtil
1597: .getFileDisplayName(od.getPrimaryFile()))); //NOI18N
1598: }
1599: }
1600:
1601: private static class Request {
1602: private final CancellableTask<? extends CompilationInfo> task;
1603: private final JavaSource javaSource; //XXX: Maybe week, depends on the semantics
1604: private final Phase phase;
1605: private final Priority priority;
1606: private final boolean reschedule;
1607:
1608: public Request(
1609: final CancellableTask<? extends CompilationInfo> task,
1610: final JavaSource javaSource, final Phase phase,
1611: final Priority priority, final boolean reschedule) {
1612: assert task != null;
1613: this .task = task;
1614: this .javaSource = javaSource;
1615: this .phase = phase;
1616: this .priority = priority;
1617: this .reschedule = reschedule;
1618: }
1619:
1620: public @Override
1621: String toString() {
1622: if (reschedule) {
1623: return String
1624: .format(
1625: "Periodic request for phase: %s with priority: %s to perform: %s",
1626: phase.name(), priority, task.toString()); //NOI18N
1627: } else {
1628: return String
1629: .format(
1630: "One time request for phase: %s with priority: %s to perform: %s",
1631: phase != null ? phase.name() : "<null>",
1632: priority, task.toString()); //NOI18N
1633: }
1634: }
1635:
1636: public @Override
1637: int hashCode() {
1638: return this .priority.ordinal();
1639: }
1640:
1641: public @Override
1642: boolean equals(Object other) {
1643: if (other instanceof Request) {
1644: Request otherRequest = (Request) other;
1645: return priority == otherRequest.priority
1646: && reschedule == otherRequest.reschedule
1647: && (phase == null ? otherRequest.phase == null
1648: : phase.equals(otherRequest.phase))
1649: && task.equals(otherRequest.task);
1650: } else {
1651: return false;
1652: }
1653: }
1654: }
1655:
1656: private static class RequestComparator implements
1657: Comparator<Request> {
1658: public int compare(Request r1, Request r2) {
1659: assert r1 != null && r2 != null;
1660: return r1.priority.compareTo(r2.priority);
1661: }
1662: }
1663:
1664: private static class CompilationJob implements Runnable {
1665:
1666: @SuppressWarnings("unchecked")
1667: //NOI18N
1668: public void run() {
1669: try {
1670: while (true) {
1671: try {
1672: synchronized (INTERNAL_LOCK) {
1673: //Clean up toRemove tasks
1674: if (!toRemove.isEmpty()) {
1675: for (Iterator<Collection<Request>> it = finishedRequests
1676: .values().iterator(); it
1677: .hasNext();) {
1678: Collection<Request> cr = it.next();
1679: for (Iterator<Request> it2 = cr
1680: .iterator(); it2.hasNext();) {
1681: Request fr = it2.next();
1682: if (toRemove.remove(fr.task)) {
1683: it2.remove();
1684: }
1685: }
1686: if (cr.size() == 0) {
1687: it.remove();
1688: }
1689: }
1690: }
1691: }
1692: Request r = JavaSource.requests.poll(2,
1693: TimeUnit.SECONDS);
1694: if (r != null) {
1695: currentRequest.setCurrentTask(r);
1696: try {
1697: JavaSource js = r.javaSource;
1698: if (js == null) {
1699: assert r.phase == null;
1700: assert r.reschedule == false;
1701: javacLock.lock();
1702: try {
1703: try {
1704: r.task.run(null);
1705: } finally {
1706: currentRequest
1707: .clearCurrentTask();
1708: boolean cancelled = requests
1709: .contains(r);
1710: if (!cancelled) {
1711: DeferredTask[] _todo;
1712: synchronized (todo) {
1713: _todo = todo
1714: .toArray(new DeferredTask[todo
1715: .size()]);
1716: todo.clear();
1717: }
1718: for (DeferredTask rq : _todo) {
1719: try {
1720: rq.js
1721: .runUserActionTask(
1722: rq.task,
1723: rq.shared);
1724: } finally {
1725: rq.sync
1726: .taskFinished();
1727: }
1728: }
1729: }
1730: }
1731: } catch (RuntimeException re) {
1732: Exceptions.printStackTrace(re);
1733: } finally {
1734: javacLock.unlock();
1735: }
1736: } else {
1737: assert js.files.size() <= 1;
1738: boolean jsInvalid;
1739: Pair<DocPositionRegion, MethodTree> changedMethod;
1740: CompilationInfoImpl ci;
1741: synchronized (INTERNAL_LOCK) {
1742: //jl:what does this comment mean?
1743: //Not only the finishedRequests for the current request.javaSource should be cleaned,
1744: //it will cause a starvation
1745: if (toRemove.remove(r.task)) {
1746: continue;
1747: }
1748: synchronized (js) {
1749: boolean changeExpected = (js.flags & CHANGE_EXPECTED) != 0;
1750: if (changeExpected) {
1751: //Skeep the task, another invalidation is comming
1752: Collection<Request> rc = JavaSource.waitingRequests
1753: .get(r.javaSource);
1754: if (rc == null) {
1755: rc = new LinkedList<Request>();
1756: JavaSource.waitingRequests
1757: .put(
1758: r.javaSource,
1759: rc);
1760: }
1761: rc.add(r);
1762: continue;
1763: }
1764: jsInvalid = js.currentInfo == null
1765: || (js.flags & INVALID) != 0;
1766: ci = js.currentInfo;
1767: changedMethod = ci == null ? null
1768: : ci
1769: .getChangedTree();
1770:
1771: }
1772: }
1773: try {
1774: //createCurrentInfo has to be out of synchronized block, it aquires an editor lock
1775: if (jsInvalid) {
1776: boolean needsFullReparse = true;
1777: if (changedMethod != null
1778: && ci != null) {
1779: javacLock.lock();
1780: try {
1781: needsFullReparse = !reparseMethod(
1782: ci,
1783: changedMethod.second,
1784: changedMethod.first
1785: .getText());
1786: } finally {
1787: javacLock.unlock();
1788: }
1789: }
1790: if (needsFullReparse) {
1791: ci = createCurrentInfo(
1792: js, js.binding,
1793: null);
1794: }
1795: synchronized (js) {
1796: if (js.currentInfo == null
1797: || (js.flags & INVALID) != 0) {
1798: js.currentInfo = ci;
1799: js.flags &= ~INVALID;
1800: } else {
1801: ci = js.currentInfo;
1802: }
1803: }
1804: }
1805: assert ci != null;
1806: javacLock.lock();
1807: try {
1808: boolean shouldCall;
1809: final JSCancelService cancelService = JSCancelService
1810: .instance(ci
1811: .getJavacTask()
1812: .getContext());
1813: if (cancelService != null) {
1814: cancelService.active = true;
1815: }
1816:
1817: try {
1818: final Phase phase = JavaSource
1819: .moveToPhase(
1820: r.phase,
1821: ci,
1822: true);
1823: shouldCall = phase
1824: .compareTo(r.phase) >= 0;
1825: } finally {
1826: if (cancelService != null) {
1827: cancelService.active = false;
1828: }
1829: }
1830: if (shouldCall) {
1831: synchronized (js) {
1832: shouldCall &= (js.flags & INVALID) == 0;
1833: }
1834: if (shouldCall) {
1835: //The state (or greater) was reached and document was not modified during moveToPhase
1836: try {
1837: final long startTime = System
1838: .currentTimeMillis();
1839: Index.cancel
1840: .set(currentRequest
1841: .getCanceledRef());
1842: try {
1843: final CompilationInfo clientCi = new CompilationInfo(
1844: ci);
1845: try {
1846: ((CancellableTask<CompilationInfo>) r.task)
1847: .run(clientCi); //XXX: How to do it in save way?
1848: } finally {
1849: clientCi
1850: .invalidate();
1851: }
1852: } finally {
1853: Index.cancel
1854: .remove();
1855: }
1856: final long endTime = System
1857: .currentTimeMillis();
1858: if (LOGGER
1859: .isLoggable(Level.FINEST)) {
1860: LOGGER
1861: .finest(String
1862: .format(
1863: "executed task: %s in %d ms.", //NOI18N
1864: r.task
1865: .getClass()
1866: .toString(),
1867: (endTime - startTime)));
1868: }
1869: if (LOGGER
1870: .isLoggable(Level.FINER)) {
1871: final long cancelTime = currentRequest
1872: .getCancelTime();
1873: if (cancelTime >= startTime
1874: && (endTime - cancelTime) > SLOW_CANCEL_LIMIT) {
1875: LOGGER
1876: .finer(String
1877: .format(
1878: "Task: %s ignored cancel for %d ms.", //NOI18N
1879: r.task
1880: .getClass()
1881: .toString(),
1882: (endTime - cancelTime)));
1883: }
1884: }
1885: } catch (CancelAbort ca) {
1886: //Handled below by: canceled = currentRequest.setCurrentTask(null);
1887: } catch (Exception re) {
1888: Exceptions
1889: .printStackTrace(re);
1890: }
1891: }
1892: }
1893: } finally {
1894: javacLock.unlock();
1895: }
1896:
1897: if (r.reschedule) {
1898: synchronized (INTERNAL_LOCK) {
1899: boolean canceled = currentRequest
1900: .setCurrentTask(null);
1901: synchronized (js) {
1902: if ((js.flags & INVALID) != 0
1903: || canceled) {
1904: //The JavaSource was changed or canceled rechedule it now
1905: JavaSource.requests
1906: .add(r);
1907: } else {
1908: //Up to date JavaSource add it to the finishedRequests
1909: Collection<Request> rc = JavaSource.finishedRequests
1910: .get(r.javaSource);
1911: if (rc == null) {
1912: rc = new LinkedList<Request>();
1913: JavaSource.finishedRequests
1914: .put(
1915: r.javaSource,
1916: rc);
1917: }
1918: rc.add(r);
1919: }
1920: }
1921: }
1922: }
1923: } catch (final FileObjects.InvalidFileException invalidFile) {
1924: //Ideally the requests should be removed by JavaSourceTaskFactory and task should be put to finishedRequests,
1925: //but the reality is different, the task cannot be put to finished request because of possible memory leak
1926: }
1927: }
1928: } finally {
1929: currentRequest.setCurrentTask(null);
1930: }
1931: }
1932: } catch (Throwable e) {
1933: if (e instanceof InterruptedException) {
1934: throw (InterruptedException) e;
1935: } else if (e instanceof ThreadDeath) {
1936: throw (ThreadDeath) e;
1937: } else {
1938: Exceptions.printStackTrace(e);
1939: }
1940: }
1941: }
1942: } catch (InterruptedException ie) {
1943: ie.printStackTrace();
1944: // stop the service.
1945: }
1946: }
1947: }
1948:
1949: private class DocListener implements PropertyChangeListener,
1950: ChangeListener, TokenHierarchyListener {
1951:
1952: private EditorCookie.Observable ec;
1953: private TokenHierarchyListener lexListener;
1954: private volatile Document document;
1955:
1956: public DocListener(EditorCookie.Observable ec) {
1957: assert ec != null;
1958: this .ec = ec;
1959: this .ec.addPropertyChangeListener(WeakListeners
1960: .propertyChange(this , this .ec));
1961: Document doc = ec.getDocument();
1962: if (doc != null) {
1963: TokenHierarchy th = TokenHierarchy.get(doc);
1964: th
1965: .addTokenHierarchyListener(lexListener = WeakListeners
1966: .create(TokenHierarchyListener.class,
1967: this , th));
1968: document = doc;
1969: }
1970: }
1971:
1972: public void propertyChange(PropertyChangeEvent evt) {
1973: if (EditorCookie.Observable.PROP_DOCUMENT.equals(evt
1974: .getPropertyName())) {
1975: Object old = evt.getOldValue();
1976: if (old instanceof Document && lexListener != null) {
1977: TokenHierarchy th = TokenHierarchy
1978: .get((Document) old);
1979: th.removeTokenHierarchyListener(lexListener);
1980: lexListener = null;
1981: }
1982: Document doc = ec.getDocument();
1983: if (doc != null) {
1984: TokenHierarchy th = TokenHierarchy.get(doc);
1985: th
1986: .addTokenHierarchyListener(lexListener = WeakListeners
1987: .create(
1988: TokenHierarchyListener.class,
1989: this , th));
1990: this .document = doc; //set before rescheduling task to avoid race condition
1991: resetState(true, false);
1992: } else {
1993: //reset document
1994: this .document = doc;
1995: }
1996: }
1997: }
1998:
1999: public void stateChanged(ChangeEvent e) {
2000: JavaSource.this .resetState(true, false);
2001: }
2002:
2003: public void tokenHierarchyChanged(TokenHierarchyEvent evt) {
2004: Pair<DocPositionRegion, MethodTree> changedMethod = null;
2005: if (evt.type() == TokenHierarchyEventType.MODIFICATION) {
2006: if (supportsReparse) {
2007: int start = evt.affectedStartOffset();
2008: int end = evt.affectedEndOffset();
2009: synchronized (positions) {
2010: for (Pair<DocPositionRegion, MethodTree> pe : positions) {
2011: PositionRegion p = pe.first;
2012: if (start > p.getStartOffset()
2013: && end < p.getEndOffset()) {
2014: changedMethod = pe;
2015: break;
2016: }
2017: }
2018: if (changedMethod != null) {
2019: TokenChange<JavaTokenId> change = evt
2020: .tokenChange(JavaTokenId.language());
2021: if (change != null) {
2022: TokenSequence<JavaTokenId> ts = change
2023: .removedTokenSequence();
2024: if (ts != null) {
2025: while (ts.moveNext()) {
2026: switch (ts.token().id()) {
2027: case LBRACE:
2028: case RBRACE:
2029: changedMethod = null;
2030: break;
2031: }
2032: }
2033: }
2034: if (changedMethod != null) {
2035: TokenSequence<JavaTokenId> current = change
2036: .currentTokenSequence();
2037: current.moveIndex(change.index());
2038: for (int i = 0; i < change
2039: .addedTokenCount(); i++) {
2040: current.moveNext();
2041: switch (current.token().id()) {
2042: case LBRACE:
2043: case RBRACE:
2044: changedMethod = null;
2045: break;
2046: }
2047: }
2048: }
2049: }
2050: }
2051: positions.clear();
2052: if (changedMethod != null) {
2053: positions.add(changedMethod);
2054: }
2055: }
2056: }
2057: }
2058: JavaSource.this .resetState(true, changedMethod == null,
2059: changedMethod);
2060: }
2061: }
2062:
2063: private static class EditorRegistryListener implements
2064: CaretListener, PropertyChangeListener {
2065:
2066: private Request request;
2067: private JTextComponent lastEditor;
2068:
2069: public EditorRegistryListener() {
2070: EditorRegistry
2071: .addPropertyChangeListener(new PropertyChangeListener() {
2072: public void propertyChange(
2073: PropertyChangeEvent evt) {
2074: editorRegistryChanged();
2075: }
2076: });
2077: editorRegistryChanged();
2078: }
2079:
2080: public void editorRegistryChanged() {
2081: final JTextComponent editor = EditorRegistry
2082: .lastFocusedComponent();
2083: if (lastEditor != editor) {
2084: if (lastEditor != null) {
2085: lastEditor.removeCaretListener(this );
2086: lastEditor.removePropertyChangeListener(this );
2087: final Document doc = lastEditor.getDocument();
2088: JavaSource js = null;
2089: if (doc != null) {
2090: js = forDocument(doc);
2091: }
2092: if (js != null) {
2093: js.k24 = false;
2094: }
2095: }
2096: lastEditor = editor;
2097: if (lastEditor != null) {
2098: lastEditor.addCaretListener(this );
2099: lastEditor.addPropertyChangeListener(this );
2100: }
2101: }
2102: }
2103:
2104: public void caretUpdate(CaretEvent event) {
2105: if (lastEditor != null) {
2106: Document doc = lastEditor.getDocument();
2107: if (doc != null) {
2108: JavaSource js = forDocument(doc);
2109: if (js != null) {
2110: js.resetState(false, false);
2111: }
2112: }
2113: }
2114: }
2115:
2116: public void propertyChange(final PropertyChangeEvent evt) {
2117: String propName = evt.getPropertyName();
2118: if ("completion-active".equals(propName)) {
2119: JavaSource js = null;
2120: final Document doc = lastEditor.getDocument();
2121: if (doc != null) {
2122: js = forDocument(doc);
2123: }
2124: if (js != null) {
2125: Object rawValue = evt.getNewValue();
2126: assert rawValue instanceof Boolean;
2127: if (rawValue instanceof Boolean) {
2128: final boolean value = (Boolean) rawValue;
2129: if (value) {
2130: assert this .request == null;
2131: this .request = currentRequest
2132: .getTaskToCancel(false);
2133: if (this .request != null) {
2134: this .request.task.cancel();
2135: }
2136: js.k24 = true;
2137: } else {
2138: Request _request = this .request;
2139: this .request = null;
2140: js.k24 = false;
2141: js.resetTask.schedule(js.reparseDelay);
2142: currentRequest.cancelCompleted(_request);
2143: }
2144: }
2145: }
2146: }
2147: }
2148:
2149: }
2150:
2151: private class FileChangeListenerImpl extends FileChangeAdapter {
2152:
2153: public @Override
2154: void fileChanged(final FileEvent fe) {
2155: JavaSource.this .resetState(true, false);
2156: }
2157:
2158: public @Override
2159: void fileRenamed(FileRenameEvent fe) {
2160: JavaSource.this .resetState(true, false);
2161: }
2162: }
2163:
2164: private final class DataObjectListener implements
2165: PropertyChangeListener {
2166:
2167: private DataObject dobj;
2168: private final FileObject fobj;
2169: private PropertyChangeListener wlistener;
2170:
2171: public DataObjectListener(FileObject fo)
2172: throws DataObjectNotFoundException {
2173: this .fobj = fo;
2174: this .dobj = DataObject.find(fo);
2175: wlistener = WeakListeners.propertyChange(this , dobj);
2176: this .dobj.addPropertyChangeListener(wlistener);
2177: }
2178:
2179: public void propertyChange(PropertyChangeEvent pce) {
2180: DataObject invalidDO = (DataObject) pce.getSource();
2181: if (invalidDO != dobj)
2182: return;
2183: if (DataObject.PROP_VALID.equals(pce.getPropertyName())) {
2184: handleInvalidDataObject(invalidDO);
2185: } else if (pce.getPropertyName() == null && !dobj.isValid()) {
2186: handleInvalidDataObject(invalidDO);
2187: }
2188: }
2189:
2190: private void handleInvalidDataObject(final DataObject invalidDO) {
2191: RequestProcessor.getDefault().post(new Runnable() {
2192: public void run() {
2193: handleInvalidDataObjectImpl(invalidDO);
2194: }
2195: });
2196: }
2197:
2198: private void handleInvalidDataObjectImpl(DataObject invalidDO) {
2199: invalidDO.removePropertyChangeListener(wlistener);
2200: if (fobj.isValid()) {
2201: // file object still exists try to find new data object
2202: try {
2203: DataObject dobjNew = DataObject.find(fobj);
2204: synchronized (DataObjectListener.this ) {
2205: if (dobjNew == dobj) {
2206: return;
2207: }
2208: dobj = dobjNew;
2209: dobj.addPropertyChangeListener(wlistener);
2210: }
2211: assignDocumentListener(dobjNew);
2212: resetState(true, true);
2213: } catch (DataObjectNotFoundException e) {
2214: //Ignore - invalidated after fobj.isValid () was called
2215: } catch (IOException ex) {
2216: // should not occur
2217: LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
2218: }
2219: }
2220: }
2221:
2222: }
2223:
2224: private final class FilterListener implements ChangeListener {
2225:
2226: public FilterListener(final JavaFileFilterImplementation filter) {
2227: filter
2228: .addChangeListener(WeakListeners.change(this ,
2229: filter));
2230: }
2231:
2232: public void stateChanged(ChangeEvent event) {
2233: JavaSource.this .resetState(true, false);
2234: }
2235: }
2236:
2237: private static CompilationInfoImpl createCurrentInfo(
2238: final JavaSource js, final PositionConverter binding,
2239: final JavacTaskImpl javac) throws IOException {
2240: CompilationInfoImpl info = new CompilationInfoImpl(js, binding,
2241: javac);
2242: if (binding != null) {
2243: Logger.getLogger("TIMER").log(Level.FINE,
2244: "CompilationInfo",
2245: new Object[] { binding.getFileObject(), info });
2246: }
2247: return info;
2248: }
2249:
2250: private static void handleAddRequest(final Request nr) {
2251: assert nr != null;
2252: requests.add(nr);
2253: JavaSource.Request request = currentRequest
2254: .getTaskToCancel(nr.priority);
2255: try {
2256: if (request != null) {
2257: request.task.cancel();
2258: }
2259: } finally {
2260: currentRequest.cancelCompleted(request);
2261: }
2262: }
2263:
2264: private static StackTraceElement findCaller(
2265: StackTraceElement[] elements) {
2266: for (StackTraceElement e : elements) {
2267: if (JavaSource.class.getName().equals(e.getClassName())) {
2268: continue;
2269: }
2270:
2271: if (e.getClassName().startsWith("java.lang.")) {
2272: continue;
2273: }
2274:
2275: return e;
2276: }
2277:
2278: return null;
2279: }
2280:
2281: private static class SingleThreadFactory implements ThreadFactory {
2282:
2283: private Thread t;
2284:
2285: public Thread newThread(Runnable r) {
2286: assert this .t == null;
2287: this .t = new Thread(r, "Java Source Worker Thread"); //NOI18N
2288: return this .t;
2289: }
2290:
2291: public boolean isDispatchThread(Thread t) {
2292: assert t != null;
2293: return this .t == t;
2294: }
2295: }
2296:
2297: private static class JavaSourceAccessorImpl extends
2298: JavaSourceAccessor {
2299:
2300: private StackTraceElement[] javacLockedStackTrace;
2301:
2302: protected @Override
2303: void runSpecialTaskImpl(CancellableTask<CompilationInfo> task,
2304: Priority priority) {
2305: handleAddRequest(new Request(task, null, null, priority,
2306: false));
2307: }
2308:
2309: @Override
2310: public JavacTaskImpl createJavacTask(
2311: ClasspathInfo cpInfo,
2312: DiagnosticListener<? super JavaFileObject> diagnosticListener,
2313: String sourceLevel, ClassNamesForFileOraculum oraculum) {
2314: if (sourceLevel == null)
2315: sourceLevel = JavaPlatformManager.getDefault()
2316: .getDefaultPlatform().getSpecification()
2317: .getVersion().toString();
2318: return JavaSource.createJavacTask(cpInfo,
2319: diagnosticListener, sourceLevel, true, oraculum);
2320: }
2321:
2322: @Override
2323: public JavacTaskImpl getJavacTask(
2324: final CompilationInfo compilationInfo) {
2325: assert compilationInfo != null;
2326: return compilationInfo.impl.getJavacTask();
2327: }
2328:
2329: @Override
2330: public CompilationInfo getCurrentCompilationInfo(
2331: final JavaSource js, final Phase phase)
2332: throws IOException {
2333: assert js != null;
2334: assert isDispatchThread();
2335: CompilationInfoImpl info = null;
2336: synchronized (js) {
2337: if ((js.flags & INVALID) == 0) {
2338: info = js.currentInfo;
2339: }
2340: }
2341: if (info == null) {
2342: return null;
2343: }
2344: Phase currentPhase = moveToPhase(phase, info, true);
2345: return currentPhase.compareTo(phase) < 0 ? null
2346: : new CompilationInfo(info);
2347: }
2348:
2349: @Override
2350: public void revalidate(JavaSource js) {
2351: js.revalidate();
2352: }
2353:
2354: @Override
2355: public boolean isDispatchThread() {
2356: return factory.isDispatchThread(Thread.currentThread());
2357: }
2358:
2359: @Override
2360: public void lockJavaCompiler() {
2361: javacLock.lock();
2362: try {
2363: this .javacLockedStackTrace = Thread.currentThread()
2364: .getStackTrace();
2365: } catch (RuntimeException e) {
2366: //Not important, thrown by logging code
2367: }
2368: }
2369:
2370: @Override
2371: public void unlockJavaCompiler() {
2372: try {
2373: this .javacLockedStackTrace = null;
2374: } finally {
2375: javacLock.unlock();
2376: }
2377: }
2378:
2379: @Override
2380: public boolean isJavaCompilerLocked() {
2381: return javacLock.isLocked();
2382: }
2383:
2384: public JavaSource create(ClasspathInfo cpInfo,
2385: PositionConverter binding,
2386: Collection<? extends FileObject> files)
2387: throws IllegalArgumentException {
2388: return JavaSource.create(cpInfo, binding, files);
2389: }
2390:
2391: public PositionConverter create(FileObject fo, int offset,
2392: int length, JTextComponent component) {
2393: return new PositionConverter(fo, offset, length, component);
2394: }
2395: }
2396:
2397: /**
2398: * Only encapsulates current request. May be trasformed into
2399: * JavaSource private static methods, but it may be less readable.
2400: */
2401: private static final class CurrentRequestReference {
2402:
2403: private static JavaSource.Request DUMMY_RQ = new JavaSource.Request(
2404: new CancellableTask<CompilationInfo>() {
2405: public void cancel() {
2406: };
2407:
2408: public void run(CompilationInfo info) {
2409: }
2410: }, null, null, null, false);
2411:
2412: private JavaSource.Request reference;
2413: private JavaSource.Request canceledReference;
2414: private long cancelTime;
2415: private final AtomicBoolean canceled;
2416: private boolean mayCancelJavac;
2417:
2418: CurrentRequestReference() {
2419: this .canceled = new AtomicBoolean();
2420: }
2421:
2422: boolean setCurrentTask(JavaSource.Request reference)
2423: throws InterruptedException {
2424: boolean result = false;
2425: synchronized (INTERNAL_LOCK) {
2426: while (this .canceledReference != null) {
2427: INTERNAL_LOCK.wait();
2428: }
2429: result = this .canceled.getAndSet(false);
2430: this .mayCancelJavac = false;
2431: this .cancelTime = 0;
2432: this .reference = reference;
2433: }
2434: return result;
2435: }
2436:
2437: /**
2438: * Prevents race-condition in runWhenScanFinished. This method may be called only from
2439: * the Java-Source-Worker-Thread right after the initial scan finished. The problem was
2440: * that the task was added into the todo after the todo was drained into the list of pending
2441: * tasks but the getTaskToCancel thought that the task is still the RepositoryUpdater. So the
2442: * Java-Source-Worker-Thread has to clean the task after calling RU.run but before draining the
2443: * pending tasks into the array, it cannot use setCurrentTaks (null) since it is under javac lock
2444: * and the setCurrentTaks methods may block the caller thread => deadlock.
2445: */
2446: void clearCurrentTask() {
2447: synchronized (INTERNAL_LOCK) {
2448: this .reference = null;
2449: }
2450: }
2451:
2452: JavaSource.Request getTaskToCancel(final Priority priority) {
2453: JavaSource.Request request = null;
2454: if (!factory.isDispatchThread(Thread.currentThread())) {
2455: synchronized (INTERNAL_LOCK) {
2456: if (this .reference != null
2457: && priority
2458: .compareTo(this .reference.priority) < 0) {
2459: assert this .canceledReference == null;
2460: request = this .reference;
2461: this .canceledReference = request;
2462: this .reference = null;
2463: this .canceled.set(true);
2464: this .cancelTime = System.currentTimeMillis();
2465: }
2466: }
2467: }
2468: return request;
2469: }
2470:
2471: JavaSource.Request getTaskToCancel(final boolean mayCancelJavac) {
2472: JavaSource.Request request = null;
2473: if (!factory.isDispatchThread(Thread.currentThread())) {
2474: synchronized (INTERNAL_LOCK) {
2475: if (this .reference != null) {
2476: assert this .canceledReference == null;
2477: request = this .reference;
2478: this .canceledReference = request;
2479: this .reference = null;
2480: this .canceled.set(true);
2481: this .mayCancelJavac = mayCancelJavac;
2482: this .cancelTime = System.currentTimeMillis();
2483: } else if (canceledReference == null) {
2484: request = DUMMY_RQ;
2485: this .canceledReference = request;
2486: this .mayCancelJavac = mayCancelJavac;
2487: this .cancelTime = System.currentTimeMillis();
2488: }
2489: }
2490: }
2491: return request;
2492: }
2493:
2494: JavaSource.Request getTaskToCancel(final CancellableTask task) {
2495: JavaSource.Request request = null;
2496: if (!factory.isDispatchThread(Thread.currentThread())) {
2497: synchronized (INTERNAL_LOCK) {
2498: if (this .reference != null
2499: && task == this .reference.task) {
2500: assert this .canceledReference == null;
2501: request = this .reference;
2502: this .canceledReference = request;
2503: this .reference = null;
2504: this .canceled.set(true);
2505: }
2506: }
2507: }
2508: return request;
2509: }
2510:
2511: JavaSource.Request getTaskToCancel() {
2512: JavaSource.Request request = null;
2513: if (!factory.isDispatchThread(Thread.currentThread())) {
2514: synchronized (INTERNAL_LOCK) {
2515: request = this .reference;
2516: if (request != null) {
2517: assert this .canceledReference == null;
2518: this .canceledReference = request;
2519: this .reference = null;
2520: this .canceled.set(true);
2521: this .cancelTime = System.currentTimeMillis();
2522: }
2523: }
2524: }
2525: return request;
2526: }
2527:
2528: /**
2529: * Called by {@link JavaSource#runWhenScanFinished} to find out which
2530: * task is currently running. Returns true when the running task in backgroud
2531: * scan otherwise returns false. The caller is expected not to call cancel on
2532: * the background scanner, so this method do not reset reference and do not set
2533: * cancelled flag when running task is background scan. But it sets the canceledReference
2534: * to prevent java source thread to dispatch next queued task.
2535: * @param request is filled by currently running task or null when there is no running task.
2536: * @return true when running task is background scan
2537: */
2538: boolean getUserTaskToCancel(JavaSource.Request[] request) {
2539: assert request != null;
2540: assert request.length == 1;
2541: boolean result = false;
2542: if (!factory.isDispatchThread(Thread.currentThread())) {
2543: synchronized (INTERNAL_LOCK) {
2544: request[0] = this .reference;
2545: if (request[0] != null) {
2546: result = request[0].phase == null;
2547: assert this .canceledReference == null;
2548: if (!result) {
2549: this .canceledReference = request[0];
2550: this .reference = null;
2551: }
2552: this .canceled.set(result);
2553: this .cancelTime = System.currentTimeMillis();
2554: }
2555: }
2556: }
2557: return result;
2558: }
2559:
2560: boolean isCanceled() {
2561: synchronized (INTERNAL_LOCK) {
2562: return this .canceled.get();
2563: }
2564: }
2565:
2566: AtomicBoolean getCanceledRef() {
2567: return this .canceled;
2568: }
2569:
2570: boolean isInterruptJavac() {
2571: synchronized (INTERNAL_LOCK) {
2572: boolean ret = this .mayCancelJavac
2573: && this .canceledReference != null
2574: && this .canceledReference.javaSource != null
2575: && (this .canceledReference.javaSource.flags & INVALID) != 0;
2576: return ret;
2577: }
2578: }
2579:
2580: long getCancelTime() {
2581: synchronized (INTERNAL_LOCK) {
2582: return this .cancelTime;
2583: }
2584: }
2585:
2586: void cancelCompleted(final JavaSource.Request request) {
2587: if (request != null) {
2588: synchronized (INTERNAL_LOCK) {
2589: assert request == this .canceledReference;
2590: this .canceledReference = null;
2591: INTERNAL_LOCK.notify();
2592: }
2593: }
2594: }
2595: }
2596:
2597: static final class ScanSync implements Future<Void> {
2598:
2599: private Task<CompilationController> task;
2600: private final CountDownLatch sync;
2601: private final AtomicBoolean canceled;
2602:
2603: public ScanSync(final Task<CompilationController> task) {
2604: assert task != null;
2605: this .task = task;
2606: this .sync = new CountDownLatch(1);
2607: this .canceled = new AtomicBoolean(false);
2608: }
2609:
2610: public boolean cancel(boolean mayInterruptIfRunning) {
2611: if (this .sync.getCount() == 0) {
2612: return false;
2613: }
2614: synchronized (todo) {
2615: boolean _canceled = canceled.getAndSet(true);
2616: if (!_canceled) {
2617: for (Iterator<DeferredTask> it = todo.iterator(); it
2618: .hasNext();) {
2619: DeferredTask task = it.next();
2620: if (task.task == this .task) {
2621: it.remove();
2622: return true;
2623: }
2624: }
2625: }
2626: }
2627: return false;
2628: }
2629:
2630: public boolean isCancelled() {
2631: return this .canceled.get();
2632: }
2633:
2634: public synchronized boolean isDone() {
2635: return this .sync.getCount() == 0;
2636: }
2637:
2638: public Void get() throws InterruptedException,
2639: ExecutionException {
2640: this .sync.await();
2641: return null;
2642: }
2643:
2644: public Void get(long timeout, TimeUnit unit)
2645: throws InterruptedException, ExecutionException,
2646: TimeoutException {
2647: this .sync.await(timeout, unit);
2648: return null;
2649: }
2650:
2651: private void taskFinished() {
2652: this .sync.countDown();
2653: }
2654: }
2655:
2656: static final class DeferredTask {
2657: final JavaSource js;
2658: final Task<CompilationController> task;
2659: final boolean shared;
2660: final ScanSync sync;
2661:
2662: public DeferredTask(final JavaSource js,
2663: final Task<CompilationController> task,
2664: final boolean shared, final ScanSync sync) {
2665: assert js != null;
2666: assert task != null;
2667: assert sync != null;
2668:
2669: this .js = js;
2670: this .task = task;
2671: this .shared = shared;
2672: this .sync = sync;
2673: }
2674: }
2675:
2676: /**
2677: * Only for unit tests
2678: */
2679: static interface JavaFileObjectProvider {
2680: public JavaFileObject createJavaFileObject(FileObject fo,
2681: FileObject root, JavaFileFilterImplementation filter)
2682: throws IOException;
2683:
2684: public void update(JavaFileObject jfo) throws IOException;
2685: }
2686:
2687: static final class DefaultJavaFileObjectProvider implements
2688: JavaFileObjectProvider {
2689: public JavaFileObject createJavaFileObject(FileObject fo,
2690: FileObject root, JavaFileFilterImplementation filter)
2691: throws IOException {
2692: return FileObjects.nbFileObject(fo, root, filter, true);
2693: }
2694:
2695: public void update(final JavaFileObject jfo) throws IOException {
2696: assert jfo instanceof SourceFileObject;
2697: ((SourceFileObject) jfo).update();
2698: }
2699:
2700: }
2701:
2702: private static class LMListener implements LowMemoryListener {
2703: private AtomicBoolean lowMemory = new AtomicBoolean(false);
2704:
2705: public void lowMemory(LowMemoryEvent event) {
2706: lowMemory.set(true);
2707: }
2708: }
2709:
2710: private static class JSCancelService extends CancelService {
2711:
2712: boolean active;
2713:
2714: public static JSCancelService instance(final Context context) {
2715: final CancelService cancelService = CancelService
2716: .instance(context);
2717: return (cancelService instanceof JSCancelService) ? (JSCancelService) cancelService
2718: : null;
2719: }
2720:
2721: static void preRegister(final Context context) {
2722: context.put(cancelServiceKey, new JSCancelService());
2723: }
2724:
2725: @Override
2726: public boolean isCanceled() {
2727: final boolean res = active
2728: && currentRequest.isInterruptJavac();
2729: return res;
2730: }
2731:
2732: }
2733:
2734: private static class JSFlowListener extends FlowListener {
2735:
2736: private boolean flowCompleted;
2737:
2738: public static JSFlowListener instance(final Context context) {
2739: final FlowListener flowListener = FlowListener
2740: .instance(context);
2741: return (flowListener instanceof JSFlowListener) ? (JSFlowListener) flowListener
2742: : null;
2743: }
2744:
2745: static void preRegister(final Context context) {
2746: context.put(flowListenerKey, new JSFlowListener());
2747: }
2748:
2749: final boolean hasFlowCompleted() {
2750: return this .flowCompleted;
2751: }
2752:
2753: @Override
2754: public void flowFinished(final Env<AttrContext> env) {
2755: this .flowCompleted = true;
2756: }
2757: }
2758:
2759: /**
2760: *Ugly and slow, called only when -ea
2761: *
2762: */
2763: private static boolean holdsDocumentWriteLock(
2764: Iterable<FileObject> files) {
2765: final Class<AbstractDocument> docClass = AbstractDocument.class;
2766: try {
2767: final Method method = docClass
2768: .getDeclaredMethod("getCurrentWriter"); //NOI18N
2769: method.setAccessible(true);
2770: final Thread currentThread = Thread.currentThread();
2771: for (FileObject fo : files) {
2772: try {
2773: final DataObject dobj = DataObject.find(fo);
2774: final EditorCookie ec = dobj
2775: .getCookie(EditorCookie.class);
2776: if (ec != null) {
2777: final StyledDocument doc = ec.getDocument();
2778: if (doc instanceof AbstractDocument) {
2779: Object result = method.invoke(doc);
2780: if (result == currentThread) {
2781: return true;
2782: }
2783: }
2784: }
2785: } catch (Exception e) {
2786: Exceptions.printStackTrace(e);
2787: }
2788: }
2789: } catch (NoSuchMethodException e) {
2790: Exceptions.printStackTrace(e);
2791: }
2792: return false;
2793: }
2794:
2795: private static String validateSourceLevel(String sourceLevel) {
2796: Source[] sources = Source.values();
2797: if (sourceLevel == null) {
2798: //Should never happen but for sure
2799: return sources[sources.length - 1].name;
2800: }
2801: for (Source source : sources) {
2802: if (source.name.equals(sourceLevel)) {
2803: return sourceLevel;
2804: }
2805: }
2806: SpecificationVersion specVer = new SpecificationVersion(
2807: sourceLevel);
2808: SpecificationVersion JAVA_12 = new SpecificationVersion("1.2"); //NOI18N
2809: if (JAVA_12.compareTo(specVer) > 0) {
2810: //Some SourceLevelQueries return 1.1 source level which is invalid, use 1.2
2811: return sources[0].name;
2812: } else {
2813: return sources[sources.length - 1].name;
2814: }
2815: }
2816:
2817: private static final int MAX_DUMPS = 255;
2818:
2819: /**
2820: * Dumps the source code to the file. Used for parser debugging. Only a limited number
2821: * of dump files is used. If the last file exists, this method doesn't dump anything.
2822: *
2823: * @param info CompilationInfo for which the error occurred.
2824: * @param exc exception to write to the end of dump file
2825: */
2826: private static void dumpSource(CompilationInfoImpl info,
2827: Throwable exc) {
2828: String userDir = System.getProperty("netbeans.user");
2829: if (userDir == null) {
2830: return;
2831: }
2832: String dumpDir = userDir + "/var/log/"; //NOI18N
2833: String src = info.getText();
2834: FileObject file = info.getFileObject();
2835: String fileName = FileUtil.getFileDisplayName(file);
2836: String origName = file.getName();
2837: File f = new File(dumpDir + origName + ".dump"); // NOI18N
2838: boolean dumpSucceeded = false;
2839: int i = 1;
2840: while (i < MAX_DUMPS) {
2841: if (!f.exists())
2842: break;
2843: f = new File(dumpDir + origName + '_' + i + ".dump"); // NOI18N
2844: i++;
2845: }
2846: if (!f.exists()) {
2847: try {
2848: OutputStream os = new FileOutputStream(f);
2849: PrintWriter writer = new PrintWriter(
2850: new OutputStreamWriter(os, "UTF-8")); // NOI18N
2851: try {
2852: writer.println(src);
2853: writer
2854: .println("----- Classpath: ---------------------------------------------"); // NOI18N
2855:
2856: final ClassPath bootPath = info.getClasspathInfo()
2857: .getClassPath(ClasspathInfo.PathKind.BOOT);
2858: final ClassPath classPath = info.getClasspathInfo()
2859: .getClassPath(
2860: ClasspathInfo.PathKind.COMPILE);
2861: final ClassPath sourcePath = info
2862: .getClasspathInfo().getClassPath(
2863: ClasspathInfo.PathKind.SOURCE);
2864:
2865: writer.println("bootPath: "
2866: + (bootPath != null ? bootPath.toString()
2867: : "null"));
2868: writer.println("classPath: "
2869: + (classPath != null ? classPath.toString()
2870: : "null"));
2871: writer.println("sourcePath: "
2872: + (sourcePath != null ? sourcePath
2873: .toString() : "null"));
2874:
2875: writer
2876: .println("----- Original exception ---------------------------------------------"); // NOI18N
2877: exc.printStackTrace(writer);
2878: } finally {
2879: writer.close();
2880: dumpSucceeded = true;
2881: }
2882: } catch (IOException ioe) {
2883: LOGGER.log(Level.INFO,
2884: "Error when writing parser dump file!", ioe); // NOI18N
2885: }
2886: }
2887: if (dumpSucceeded) {
2888: Throwable t = Exceptions
2889: .attachMessage(
2890: exc,
2891: "An error occurred during parsing of \'"
2892: + fileName
2893: + "\'. Please report a bug against java/source and attach dump file '" // NOI18N
2894: + f.getAbsolutePath() + "'."); // NOI18N
2895: Exceptions.printStackTrace(t);
2896: } else {
2897: LOGGER
2898: .log(Level.WARNING,
2899: "Dump could not be written. Either dump file could not "
2900: + // NOI18N
2901: "be created or all dump files were already used. Please "
2902: + // NOI18N
2903: "check that you have write permission to '"
2904: + dumpDir + "' and " + // NOI18N
2905: "clean all *.dump files in that directory."); // NOI18N
2906: }
2907: }
2908:
2909: private static final class DevNullWriter extends Writer {
2910: public void write(char[] cbuf, int off, int len)
2911: throws IOException {
2912: }
2913:
2914: public void flush() throws IOException {
2915: }
2916:
2917: public void close() throws IOException {
2918: }
2919: }
2920:
2921: private static class FindMethodRegionsVisitor extends
2922: SimpleTreeVisitor<Void, Void> {
2923:
2924: final Document doc;
2925: final SourcePositions pos;
2926: CompilationUnitTree cu;
2927:
2928: final List<Pair<DocPositionRegion, MethodTree>> posRegions = new LinkedList<Pair<JavaSource.DocPositionRegion, MethodTree>>();
2929:
2930: public FindMethodRegionsVisitor(final Document doc,
2931: final SourcePositions pos) {
2932: assert doc != null;
2933: assert pos != null;
2934: this .doc = doc;
2935: this .pos = pos;
2936: }
2937:
2938: @Override
2939: public Void visitCompilationUnit(CompilationUnitTree node,
2940: Void p) {
2941: cu = node;
2942: for (Tree t : node.getTypeDecls()) {
2943: visit(t, p);
2944: }
2945: return null;
2946: }
2947:
2948: @Override
2949: public Void visitClass(ClassTree node, Void p) {
2950: for (Tree t : node.getMembers()) {
2951: visit(t, p);
2952: }
2953: return null;
2954: }
2955:
2956: @Override
2957: public Void visitMethod(MethodTree node, Void p) {
2958: assert cu != null;
2959: int startPos = (int) pos.getStartPosition(cu, node
2960: .getBody());
2961: int endPos = (int) pos.getEndPosition(cu, node.getBody());
2962: if (startPos >= 0) {
2963: try {
2964: posRegions.add(Pair
2965: .<DocPositionRegion, MethodTree> of(
2966: new DocPositionRegion(doc,
2967: startPos, endPos), node));
2968: } catch (BadLocationException e) {
2969: //todo: reocvery
2970: e.printStackTrace();
2971: }
2972: }
2973: return null;
2974: }
2975:
2976: }
2977:
2978: private static boolean reparseMethod(final CompilationInfoImpl ci,
2979: final MethodTree orig, final String newBody)
2980: throws IOException {
2981: assert ci != null;
2982: final FileObject fo = ci.getFileObject();
2983: if (LOGGER.isLoggable(Level.FINER)) {
2984: LOGGER.finer("Reparse method in: " + fo); //NOI18N
2985: }
2986: if (!ci.getJavaSource().supportsReparse) {
2987: return false;
2988: }
2989: if (((JCMethodDecl) orig).localEnv == null) {
2990: //We are seeing interface method or abstract or native method with body.
2991: //Don't do any optimalization of this broken code - has no attr env.
2992: return false;
2993: }
2994: final Phase currentPhase = ci.getPhase();
2995: if (Phase.PARSED.compareTo(currentPhase) > 0) {
2996: return false;
2997: }
2998: try {
2999: final CompilationUnitTree cu = ci.getCompilationUnit();
3000: if (cu == null || newBody == null) {
3001: return false;
3002: }
3003: final JavacTaskImpl task = ci.getJavacTask();
3004: final JavacTrees jt = JavacTrees.instance(task);
3005: final int origStartPos = (int) jt.getSourcePositions()
3006: .getStartPosition(cu, orig.getBody());
3007: final int origEndPos = (int) jt.getSourcePositions()
3008: .getEndPosition(cu, orig.getBody());
3009: if (origStartPos > origEndPos) {
3010: LOGGER.warning("Javac returned startpos: "
3011: + origStartPos + " > endpos: " + origEndPos); //NOI18N
3012: return false;
3013: }
3014: final FindAnnonVisitor fav = new FindAnnonVisitor();
3015: fav.scan(orig.getBody(), null);
3016: if (fav.hasLocalClass) {
3017: if (LOGGER.isLoggable(Level.FINER)) {
3018: LOGGER
3019: .finer("Skeep reparse method (old local classes): "
3020: + fo); //NOI18N
3021: }
3022: return false;
3023: }
3024: final int firstInner = fav.firstInner;
3025: final int noInner = fav.noInner;
3026: final Context ctx = task.getContext();
3027: final Log l = Log.instance(ctx);
3028: l.startPartialReparse();
3029: final JavaFileObject prevLogged = l.useSource(cu
3030: .getSourceFile());
3031: JCBlock block;
3032: try {
3033: DiagnosticListener dl = ctx
3034: .get(DiagnosticListener.class);
3035: assert dl instanceof CompilationInfoImpl.DiagnosticListenerImpl;
3036: ((CompilationInfoImpl.DiagnosticListenerImpl) dl)
3037: .startPartialReparse(origStartPos, origEndPos);
3038: long start = System.currentTimeMillis();
3039: block = task.reparseMethodBody(cu, orig, newBody,
3040: firstInner);
3041: if (LOGGER.isLoggable(Level.FINER)) {
3042: LOGGER.finer("Reparsed method in: " + fo); //NOI18N
3043: }
3044: assert block != null;
3045: fav.reset();
3046: fav.scan(block, null);
3047: final int newNoInner = fav.noInner;
3048: if (fav.hasLocalClass || noInner != newNoInner) {
3049: if (LOGGER.isLoggable(Level.FINER)) {
3050: LOGGER
3051: .finer("Skeep reparse method (new local classes): "
3052: + fo); //NOI18N
3053: }
3054: return false;
3055: }
3056: long end = System.currentTimeMillis();
3057: if (fo != null) {
3058: logTime(fo, Phase.PARSED, (end - start));
3059: }
3060: final int newEndPos = (int) jt.getSourcePositions()
3061: .getEndPosition(cu, block);
3062: final int delta = newEndPos - origEndPos;
3063: final Map<JCTree, Integer> endPos = ((JCCompilationUnit) cu).endPositions;
3064: final TranslatePosVisitor tpv = new TranslatePosVisitor(
3065: orig, endPos, delta);
3066: tpv.scan(cu, null);
3067: ((JCMethodDecl) orig).body = block;
3068: if (Phase.RESOLVED.compareTo(currentPhase) <= 0) {
3069: start = System.currentTimeMillis();
3070: task.reattrMethodBody(orig, block);
3071: if (LOGGER.isLoggable(Level.FINER)) {
3072: LOGGER.finer("Resolved method in: " + fo); //NOI18N
3073: }
3074: if (!((CompilationInfoImpl.DiagnosticListenerImpl) dl)
3075: .hasPartialReparseErrors()) {
3076: final JSFlowListener fl = JSFlowListener
3077: .instance(ctx);
3078: if (fl != null && fl.hasFlowCompleted()) {
3079: if (LOGGER.isLoggable(Level.FINER)) {
3080: final List<? extends Diagnostic> diag = ci
3081: .getDiagnostics();
3082: if (!diag.isEmpty()) {
3083: LOGGER.finer("Reflow with errors: "
3084: + fo + " " + diag); //NOI18N
3085: }
3086: }
3087: TreePath tp = TreePath.getPath(cu, orig); //todo: store treepath in changed method => improve speed
3088: Tree t = tp.getParentPath().getLeaf();
3089: task.reflowMethodBody(cu, (ClassTree) t,
3090: orig);
3091: if (LOGGER.isLoggable(Level.FINER)) {
3092: LOGGER.finer("Reflowed method in: "
3093: + fo); //NOI18N
3094: }
3095: }
3096: }
3097: end = System.currentTimeMillis();
3098: if (fo != null) {
3099: logTime(fo, Phase.ELEMENTS_RESOLVED, 0L);
3100: logTime(fo, Phase.RESOLVED, (end - start));
3101: }
3102: }
3103: ((CompilationInfoImpl.DiagnosticListenerImpl) dl)
3104: .endPartialReparse(delta);
3105: } finally {
3106: l.endPartialReparse();
3107: l.useSource(prevLogged);
3108: }
3109: jfoProvider.update(ci.jfo);
3110: } catch (Throwable t) {
3111: if (t instanceof ThreadDeath) {
3112: throw (ThreadDeath) t;
3113: }
3114: dumpSource(ci, t);
3115: return false;
3116: }
3117: return true;
3118: }
3119:
3120: //where
3121:
3122: private static class FindAnnonVisitor extends
3123: TreeScanner<Void, Void> {
3124: private int firstInner = -1;
3125: private int noInner;
3126: private boolean hasLocalClass;
3127:
3128: public final void reset() {
3129: this .firstInner = -1;
3130: this .noInner = 0;
3131: this .hasLocalClass = false;
3132: }
3133:
3134: @Override
3135: public Void visitClass(ClassTree node, Void p) {
3136: if (firstInner == -1) {
3137: firstInner = ((JCClassDecl) node).index;
3138: }
3139: if (node.getSimpleName().length() != 0) {
3140: hasLocalClass = true;
3141: }
3142: noInner++;
3143: return super .visitClass(node, p);
3144: }
3145:
3146: }
3147:
3148: private static class TranslatePosVisitor extends
3149: TreeScanner<Void, Void> {
3150:
3151: private final MethodTree changedMethod;
3152: private final Map<JCTree, Integer> endPos;
3153: private final int delta;
3154: boolean active;
3155: boolean inMethod;
3156:
3157: public TranslatePosVisitor(final MethodTree changedMethod,
3158: final Map<JCTree, Integer> endPos, final int delta) {
3159: assert changedMethod != null;
3160: assert endPos != null;
3161: this .changedMethod = changedMethod;
3162: this .endPos = endPos;
3163: this .delta = delta;
3164: }
3165:
3166: @Override
3167: public Void scan(Tree node, Void p) {
3168: if (active && node != null) {
3169: if (((JCTree) node).pos >= 0) {
3170: ((JCTree) node).pos += delta;
3171: }
3172: }
3173: Void result = super .scan(node, p);
3174: if (inMethod && node != null) {
3175: endPos.remove(node);
3176: }
3177: if (active && node != null) {
3178: Integer pos = endPos.remove(node);
3179: if (pos != null) {
3180: int newPos;
3181: if (pos < 0) {
3182: newPos = pos;
3183: } else {
3184: newPos = pos + delta;
3185: }
3186: endPos.put((JCTree) node, newPos);
3187: }
3188: }
3189: return result;
3190: }
3191:
3192: @Override
3193: public Void visitCompilationUnit(CompilationUnitTree node,
3194: Void p) {
3195: return scan(node.getTypeDecls(), p);
3196: }
3197:
3198: @Override
3199: public Void visitMethod(MethodTree node, Void p) {
3200: if (active || inMethod) {
3201: scan(node.getModifiers(), p);
3202: scan(node.getReturnType(), p);
3203: scan(node.getTypeParameters(), p);
3204: scan(node.getParameters(), p);
3205: scan(node.getThrows(), p);
3206: }
3207: if (node == changedMethod) {
3208: inMethod = true;
3209: }
3210: if (active || inMethod) {
3211: scan(node.getBody(), p);
3212: }
3213: if (inMethod) {
3214: active = inMethod;
3215: inMethod = false;
3216: }
3217: if (active || inMethod) {
3218: scan(node.getDefaultValue(), p);
3219: }
3220: return null;
3221: }
3222:
3223: }
3224:
3225: static final class DocPositionRegion extends PositionRegion {
3226:
3227: private final Document doc;
3228:
3229: public DocPositionRegion(final Document doc,
3230: final int startPos, final int endPos)
3231: throws BadLocationException {
3232: super (doc, startPos, endPos);
3233: assert doc != null;
3234: this .doc = doc;
3235: }
3236:
3237: public Document getDocument() {
3238: return this .doc;
3239: }
3240:
3241: public String getText() {
3242: final String[] result = new String[1];
3243: this .doc.render(new Runnable() {
3244: public void run() {
3245: try {
3246: result[0] = doc.getText(getStartOffset(),
3247: getLength());
3248: } catch (BadLocationException ex) {
3249: Exceptions.printStackTrace(ex);
3250: }
3251: }
3252: });
3253: return result[0];
3254: }
3255:
3256: }
3257:
3258: //Package private test utility methods
3259: int getReparseDelay() {
3260: return this .reparseDelay;
3261: }
3262:
3263: void setReparseDelay(int reparseDelay, boolean reset) {
3264: this .reparseDelay = reparseDelay;
3265: if (reset) {
3266: resetState(true, false);
3267: }
3268: }
3269:
3270: }
|