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-2006 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.napi.gsfret.source;
0043:
0044: import java.beans.PropertyChangeEvent;
0045: import java.beans.PropertyChangeListener;
0046: import java.io.File;
0047: import java.io.FileOutputStream;
0048: import java.io.IOException;
0049: import java.io.OutputStream;
0050: import java.io.OutputStreamWriter;
0051: import java.io.PrintWriter;
0052: import java.lang.ref.Reference;
0053: import java.lang.ref.WeakReference;
0054: import java.lang.reflect.Method;
0055: import java.util.ArrayList;
0056: import java.util.Arrays;
0057: import java.util.Collection;
0058: import java.util.Collections;
0059: import java.util.Comparator;
0060: import java.util.HashMap;
0061: import java.util.HashSet;
0062: import java.util.Iterator;
0063: import java.util.LinkedList;
0064: import java.util.List;
0065: import java.util.Map;
0066: import java.util.WeakHashMap;
0067: import java.util.concurrent.Executors;
0068: import java.util.concurrent.PriorityBlockingQueue;
0069: import java.util.concurrent.ThreadFactory;
0070: import java.util.concurrent.TimeUnit;
0071: import java.util.concurrent.atomic.AtomicBoolean;
0072: import java.util.concurrent.locks.ReentrantLock;
0073: import java.util.logging.Level;
0074: import java.util.logging.Logger;
0075: import javax.swing.event.ChangeEvent;
0076: import javax.swing.event.ChangeListener;
0077: import javax.swing.event.DocumentEvent;
0078: import javax.swing.event.DocumentListener;
0079: import javax.swing.text.AbstractDocument;
0080: import javax.swing.text.Document;
0081: import javax.swing.text.StyledDocument;
0082: import javax.swing.event.CaretEvent;
0083: import javax.swing.event.CaretListener;
0084: import javax.swing.event.ChangeListener;
0085: import javax.swing.text.JTextComponent;
0086: import org.netbeans.api.editor.EditorRegistry;
0087: import org.netbeans.modules.gsf.api.ParserFile;
0088: import org.netbeans.modules.gsf.api.TranslatedSource;
0089: import org.netbeans.modules.gsfpath.api.classpath.ClassPath;
0090: import org.netbeans.modules.gsfpath.api.platform.JavaPlatformManager;
0091: import org.netbeans.modules.gsfpath.api.queries.SourceLevelQuery;
0092: import org.netbeans.modules.gsf.api.Error;
0093: import org.netbeans.modules.gsf.api.ParseEvent;
0094: import org.netbeans.modules.gsf.api.ParseListener;
0095: import org.netbeans.modules.gsf.api.Parser;
0096: import org.netbeans.modules.gsf.api.ParserResult;
0097: import org.netbeans.modules.gsf.api.SourceFileReader;
0098: import org.netbeans.modules.gsf.api.CancellableTask;
0099: import org.netbeans.modules.gsf.api.EmbeddingModel;
0100: import org.netbeans.napi.gsfret.source.ClasspathInfo.PathKind;
0101: import org.netbeans.napi.gsfret.source.ModificationResult.Difference;
0102: import org.netbeans.napi.gsfret.source.ParserTaskImpl;
0103: import org.netbeans.napi.gsfret.source.support.CaretAwareSourceTaskFactory; //import org.netbeans.api.timers.TimesCollector;
0104: import org.netbeans.editor.Registry;
0105: import org.netbeans.modules.gsf.Language;
0106: import org.netbeans.modules.gsf.LanguageRegistry;
0107: import org.netbeans.modules.gsfret.source.SourceAccessor;
0108: import org.netbeans.modules.gsfret.source.parsing.SourceFileObject;
0109: import org.netbeans.modules.gsfret.source.usages.ClassIndexImpl;
0110: import org.netbeans.modules.gsfret.source.usages.ClassIndexManager;
0111: import org.netbeans.modules.gsfret.source.util.LowMemoryEvent;
0112: import org.netbeans.modules.gsfret.source.util.LowMemoryListener;
0113: import org.netbeans.modules.gsfret.source.util.LowMemoryNotifier;
0114: import org.netbeans.modules.gsf.spi.DefaultParserFile;
0115: import org.openide.cookies.EditorCookie;
0116: import org.openide.filesystems.FileChangeAdapter;
0117: import org.openide.filesystems.FileChangeListener;
0118: import org.openide.filesystems.FileEvent;
0119: import org.openide.filesystems.FileObject;
0120: import org.openide.filesystems.FileRenameEvent;
0121: import org.openide.filesystems.FileUtil;
0122: import org.openide.filesystems.FileUtil;
0123: import org.openide.loaders.DataObject;
0124: import org.openide.loaders.DataObjectNotFoundException;
0125: import org.openide.util.Exceptions;
0126: import org.openide.util.NbBundle;
0127: import org.openide.util.RequestProcessor;
0128: import org.openide.util.WeakListeners;
0129:
0130: /**
0131: * This file is originally from Retouche, the Java Support
0132: * infrastructure in NetBeans. I have modified the file as little
0133: * as possible to make merging Retouche fixes back as simple as
0134: * possible.
0135: *
0136: *
0137: * This file is based on the JavaSource class in Retouche's org.netbeans.modules.gsfpath.api.source package.
0138: * It represents an open source file in the editor.
0139: *
0140: * @author Petr Hrebejk
0141: * @author Tomas Zezula
0142: * @author Tor Norbye
0143: */
0144: public final class Source {
0145:
0146: public static enum Priority {
0147: MAX, HIGH, ABOVE_NORMAL, NORMAL, BELOW_NORMAL, LOW, MIN
0148: }
0149:
0150: /**
0151: * This specialization of {@link IOException} signals that a {@link Source#runUserActionTask}
0152: * or {@link Source#runModificationTask} failed due to lack of memory. The {@link InsufficientMemoryException#getFile}
0153: * method returns a file which cannot be processed.
0154: */
0155: public static final class InsufficientMemoryException extends
0156: IOException {
0157:
0158: private FileObject fo;
0159:
0160: private InsufficientMemoryException(final String message,
0161: final FileObject fo) {
0162: super (message);
0163: this .fo = fo;
0164: }
0165:
0166: private InsufficientMemoryException(FileObject fo) {
0167: this (NbBundle.getMessage(Source.class,
0168: "MSG_UnsufficientMemoryException", FileUtil
0169: .getFileDisplayName(fo)), fo);
0170: }
0171:
0172: /**
0173: * Returns file which cannot be processed due to lack of memory.
0174: * @return {@link FileObject}
0175: */
0176: public FileObject getFile() {
0177: return this .fo;
0178: }
0179: }
0180:
0181: /**
0182: * Constants for Source.flags
0183: */
0184: private static final int INVALID = 1;
0185: private static final int CHANGE_EXPECTED = INVALID << 1;
0186: private static final int RESCHEDULE_FINISHED_TASKS = CHANGE_EXPECTED << 1;
0187: private static final int UPDATE_INDEX = RESCHEDULE_FINISHED_TASKS << 1;
0188:
0189: /**Slow task reporting*/
0190: private static final boolean reportSlowTasks = Boolean
0191: .getBoolean("org.netbeans.napi.gsfret.source.Source.reportSlowTasks"); //NOI18N
0192: /**Limit for task to be marked as a slow one, in ms*/
0193: private static final int SLOW_TASK_LIMIT = 250;
0194: private static final int SLOW_CANCEL_LIMIT = 50;
0195:
0196: /**Not final for tests.*/
0197: static int REPARSE_DELAY = 500;
0198:
0199: /**
0200: * Helper maps mapping the {@link Phase} to key and message for
0201: * the {@link TimesCollector}
0202: */
0203: private static Map<Phase, String> phase2Key = new HashMap<Phase, String>();
0204: private static Map<Phase, String> phase2Message = new HashMap<Phase, String>();
0205:
0206: /**
0207: * Init the maps
0208: */
0209: static {
0210: SourceAccessor.setINSTANCE(new JavaSourceAccessorImpl());
0211: phase2Key.put(Phase.PARSED, "parsed"); //NOI18N
0212: phase2Message.put(Phase.PARSED, "Parsed"); //NOI18N
0213:
0214: phase2Key.put(Phase.ELEMENTS_RESOLVED, "sig-attributed"); //NOI18N
0215: phase2Message.put(Phase.ELEMENTS_RESOLVED,
0216: "Signatures Attributed"); //NOI18N
0217:
0218: phase2Key.put(Phase.RESOLVED, "attributed"); //NOI18N
0219: phase2Message.put(Phase.RESOLVED, "Attributed"); //NOI18N
0220:
0221: }
0222:
0223: private final static PriorityBlockingQueue<Request> requests = new PriorityBlockingQueue<Request>(
0224: 10, new RequestComparator());
0225: private final static Map<Source, Collection<Request>> finishedRequests = new WeakHashMap<Source, Collection<Request>>();
0226: private final static Map<Source, Collection<Request>> waitingRequests = new WeakHashMap<Source, Collection<Request>>();
0227: private final static Collection<CancellableTask> toRemove = new LinkedList<CancellableTask>();
0228: private final static SingleThreadFactory factory = new SingleThreadFactory();
0229: private final static CurrentRequestReference currentRequest = new CurrentRequestReference();
0230: private final static EditorRegistryListener editorRegistryListener = new EditorRegistryListener();
0231: //Only single thread can operate on the single javac
0232: private final static ReentrantLock javacLock = new ReentrantLock(
0233: true);
0234: private final Collection<FileObject> files;
0235:
0236: private final FileObject rootFo;
0237: private final FileChangeListener fileChangeListener;
0238: private DocListener listener;
0239: private DataObjectListener dataObjectListener;
0240: private String sourceLevel;
0241:
0242: private final ClasspathInfo classpathInfo;
0243: private CompilationInfo currentInfo;
0244: private java.util.Stack<CompilationInfo> infoStack = new java.util.Stack<CompilationInfo>();
0245:
0246: private int flags = 0;
0247:
0248: //Preprocessor support
0249: private Object/*FilterListener*/filterListener;
0250:
0251: static {
0252: Executors.newSingleThreadExecutor(factory).submit(
0253: new CompilationJob());
0254: }
0255:
0256: /**
0257: * Returns a {@link Source} instance representing given {@link org.openide.filesystems.FileObject}s
0258: * and classpath represented by given {@link ClasspathInfo}.
0259: *
0260: *
0261: * @param cpInfo the classpaths to be used.
0262: * @param files for which the {@link Source} should be created
0263: * @return a new {@link Source}
0264: * @throws {@link IllegalArgumentException} if fileObject or cpInfo is null
0265: */
0266: public static Source create(final ClasspathInfo cpInfo,
0267: final Collection<? extends FileObject> files)
0268: throws IllegalArgumentException {
0269: if (files == null || cpInfo == null) {
0270: throw new IllegalArgumentException();
0271: }
0272: try {
0273: return new Source(cpInfo, files);
0274: } catch (DataObjectNotFoundException donf) {
0275: Logger.getLogger("global").warning(
0276: "Ignoring non existent file: "
0277: + FileUtil.getFileDisplayName(donf
0278: .getFileObject())); //NOI18N
0279: } catch (IOException ex) {
0280: Exceptions.printStackTrace(ex);
0281: }
0282: return null;
0283: }
0284:
0285: /**
0286: * Returns a {@link Source} instance representing given {@link org.openide.filesystems.FileObject}s
0287: * and classpath represented by given {@link ClasspathInfo}.
0288: *
0289: *
0290: * @param cpInfo the classpaths to be used.
0291: * @param files for which the {@link Source} should be created
0292: * @return a new {@link Source}
0293: * @throws {@link IllegalArgumentException} if fileObject or cpInfo is null
0294: */
0295: public static Source create(final ClasspathInfo cpInfo,
0296: final FileObject... files) throws IllegalArgumentException {
0297: if (files == null || cpInfo == null) {
0298: throw new IllegalArgumentException();
0299: }
0300: return create(cpInfo, Arrays.asList(files));
0301: }
0302:
0303: private static Map<FileObject, Reference<Source>> file2JavaSource = new WeakHashMap<FileObject, Reference<Source>>();
0304:
0305: public static void clearSourceCache() {
0306: file2JavaSource.clear();
0307: }
0308:
0309: /**
0310: * Returns a {@link Source} instance associated to given {@link org.openide.filesystems.FileObject},
0311: * it returns null if the {@link Document} is not associanted with data type providing the {@link Source}.
0312: *
0313: *
0314: * @param fileObject for which the {@link Source} should be found/created.
0315: * @return {@link Source} or null
0316: * @throws {@link IllegalArgumentException} if fileObject is null
0317: */
0318: public static Source forFileObject(FileObject fileObject)
0319: throws IllegalArgumentException {
0320: if (fileObject == null) {
0321: throw new IllegalArgumentException("fileObject == null"); //NOI18N
0322: }
0323: if (!fileObject.isValid() || fileObject.isFolder()) {
0324: return null;
0325: }
0326: if (!LanguageRegistry.getInstance().isSupported(
0327: fileObject.getMIMEType())) {
0328: return null;
0329: }
0330:
0331: Reference<Source> ref = file2JavaSource.get(fileObject);
0332: Source js = ref != null ? ref.get() : null;
0333:
0334: if (js == null) {
0335: file2JavaSource.put(fileObject, new WeakReference(
0336: js = create(ClasspathInfo.create(fileObject),
0337: fileObject)));
0338: }
0339:
0340: return js;
0341: }
0342:
0343: /**
0344: * Returns a {@link Source} instance associated to {@link org.openide.filesystems.FileObject}
0345: * the {@link Document} was created from, it returns null if the {@link Document} is not
0346: * associanted with data type providing the {@link Source}.
0347: *
0348: *
0349: * @param doc {@link Document} for which the {@link Source} should be found/created.
0350: * @return {@link Source} or null
0351: * @throws {@link IllegalArgumentException} if doc is null
0352: */
0353: public static Source forDocument(Document doc)
0354: throws IllegalArgumentException {
0355: if (doc == null) {
0356: throw new IllegalArgumentException("doc == null"); //NOI18N
0357: }
0358: Reference<Source> ref = (Reference<Source>) doc
0359: .getProperty(Source.class);
0360: Source js = ref != null ? ref.get() : null;
0361: if (js == null) {
0362: DataObject dObj = (DataObject) doc
0363: .getProperty(Document.StreamDescriptionProperty);
0364: if (dObj != null)
0365: js = forFileObject(dObj.getPrimaryFile());
0366: }
0367: return js;
0368: }
0369:
0370: /**
0371: * Creates a new instance of Source
0372: *
0373: *
0374: * @param files to create Source for
0375: * @param cpInfo classpath info
0376: */
0377: private Source(ClasspathInfo cpInfo,
0378: Collection<? extends FileObject> files) throws IOException {
0379: this .files = Collections.unmodifiableList(new ArrayList(files)); //Create a defensive copy, prevent modification
0380: this .fileChangeListener = new FileChangeListenerImpl();
0381: boolean multipleSources = this .files.size() > 1, filterAssigned = false;
0382: for (Iterator<? extends FileObject> it = this .files.iterator(); it
0383: .hasNext();) {
0384: FileObject file = it.next();
0385: try {
0386: //TimesCollector.getDefault().reportReference( file, Source.class.toString(), "[M] Source", this ); //NOI18N
0387: if (!multipleSources) {
0388: file.addFileChangeListener(FileUtil
0389: .weakFileChangeListener(
0390: this .fileChangeListener, file));
0391: this .assignDocumentListener(file);
0392: this .dataObjectListener = new DataObjectListener(
0393: file);
0394: }
0395: //if (!filterAssigned) {
0396: // filterAssigned = true;
0397: // JavaFileFilterImplementation filter = JavaFileFilterQuery.getFilter(file);
0398: // if (filter != null) {
0399: // this.filterListener = new FilterListener (filter);
0400: // }
0401: //}
0402: } catch (DataObjectNotFoundException donf) {
0403: if (multipleSources) {
0404: Logger
0405: .getLogger("global")
0406: .warning(
0407: "Ignoring non existent file: "
0408: + FileUtil
0409: .getFileDisplayName(file)); //NOI18N
0410: it.remove();
0411: } else {
0412: throw donf;
0413: }
0414: }
0415: }
0416: this .classpathInfo = cpInfo;
0417: if (files.size() == 1) {
0418: this .rootFo = classpathInfo.getClassPath(PathKind.SOURCE)
0419: .findOwnerRoot(files.iterator().next());
0420: } else {
0421: this .rootFo = null;
0422: }
0423: this .classpathInfo.addChangeListener(WeakListeners.change(
0424: this .listener, this .classpathInfo));
0425:
0426: }
0427:
0428: /** Runs a task which permits for controlling phases of the parsing process.
0429: * You probably do not want to call this method unless you are reacting to
0430: * some user's GUI input which requires immediate action (e.g. code completion popup).
0431: * In all other cases use {@link JavaSourceTaskFactory}.<BR>
0432: * Call to this method will cancel processing of all the phase completion tasks until
0433: * this task does not finish.<BR>
0434: * @see org.netbeans.napi.gsfret.source.CancellableTask for information about implementation requirements
0435: * @param task The task which.
0436: * @param shared if true the java compiler may be reused by other {@link org.netbeans.napi.gsfret.source.CancellableTasks},
0437: * the value false may have negative impact on the IDE performance.
0438: */
0439: public void runUserActionTask(
0440: final CancellableTask<CompilationController> task,
0441: final boolean shared) throws IOException {
0442: if (task == null) {
0443: throw new IllegalArgumentException("Task cannot be null"); //NOI18N
0444: }
0445:
0446: assert javacLock.isHeldByCurrentThread()
0447: || !holdsDocumentWriteLock(files) : "Source.runCompileControlTask called under Document write lock."; //NOI18N
0448:
0449: if (this .files.size() <= 1) {
0450: final Source.Request request = currentRequest
0451: .getTaskToCancel();
0452: try {
0453: if (request != null) {
0454: request.task.cancel();
0455: }
0456: this .javacLock.lock();
0457: try {
0458: CompilationInfo currentInfo = null;
0459: synchronized (this ) {
0460: if (this .currentInfo != null
0461: && (this .flags & INVALID) == 0) {
0462: currentInfo = this .currentInfo;
0463: }
0464: if (!shared) {
0465: this .flags |= INVALID;
0466: }
0467: }
0468: if (currentInfo == null) {
0469: currentInfo = createCurrentInfo(this ,
0470: this .files.isEmpty() ? null
0471: : this .files.iterator().next(),
0472: filterListener, null);
0473: if (shared) {
0474: synchronized (this ) {
0475: if (this .currentInfo == null
0476: || (this .flags & INVALID) != 0) {
0477: this .currentInfo = currentInfo;
0478: this .flags &= ~INVALID;
0479: } else {
0480: currentInfo = this .currentInfo;
0481: }
0482: }
0483: }
0484: }
0485: assert currentInfo != null;
0486: if (shared) {
0487: if (!infoStack.isEmpty()) {
0488: currentInfo = infoStack.peek();
0489: }
0490: } else {
0491: infoStack.push(currentInfo);
0492: }
0493: try {
0494: task
0495: .run(new CompilationController(
0496: currentInfo));
0497: } finally {
0498: if (!shared) {
0499: infoStack.pop();
0500: }
0501: }
0502: } catch (RuntimeException e) {
0503: throw e;
0504: } catch (Exception e) {
0505: IOException ioe = new IOException();
0506: ioe.initCause(e);
0507: throw ioe;
0508: } finally {
0509: this .javacLock.unlock();
0510: }
0511: } finally {
0512: currentRequest.cancelCompleted(request);
0513: }
0514: } else {
0515: final Source.Request request = currentRequest
0516: .getTaskToCancel();
0517: try {
0518: if (request != null) {
0519: request.task.cancel();
0520: }
0521: this .javacLock.lock();
0522: try {
0523: ParserTaskImpl jt = null;
0524: FileObject activeFile = null;
0525: Iterator<FileObject> files = this .files.iterator();
0526: while (files.hasNext() || activeFile != null) {
0527: boolean restarted;
0528: if (activeFile == null) {
0529: activeFile = files.next();
0530: restarted = false;
0531: } else {
0532: restarted = true;
0533: }
0534: CompilationInfo ci = createCurrentInfo(this ,
0535: activeFile, filterListener, jt);
0536: task.run(new CompilationController(ci));
0537: if (!ci.needsRestart) {
0538: jt = ci.getParserTask();
0539: // Log.instance(jt.getContext()).nerrors = 0;
0540: activeFile = null;
0541: } else {
0542: jt = null;
0543: ci = null;
0544: System.gc();
0545: if (restarted) {
0546: throw new InsufficientMemoryException(
0547: activeFile);
0548: }
0549: }
0550: }
0551: } catch (RuntimeException e) {
0552: throw e;
0553: } catch (Exception e) {
0554: IOException ioe = new IOException();
0555: ioe.initCause(e);
0556: throw ioe;
0557: } finally {
0558: this .javacLock.unlock();
0559: }
0560: } finally {
0561: currentRequest.cancelCompleted(request);
0562: }
0563: }
0564: }
0565:
0566: /** Runs a task which permits for modifying the sources.
0567: * Call to this method will cancel processig of all the phase completion tasks until
0568: * this task does not finish.<BR>
0569: * @see CancellableTask for information about implementation requirements
0570: * @param task The task which.
0571: */
0572: public ModificationResult runModificationTask(
0573: CancellableTask<WorkingCopy> task) throws IOException {
0574: if (task == null) {
0575: throw new IllegalArgumentException("Task cannot be null"); //NOI18N
0576: }
0577:
0578: assert javacLock.isHeldByCurrentThread()
0579: || !holdsDocumentWriteLock(files) : "Source.runCompileControlTask called under Document write lock."; //NOI18N
0580:
0581: ModificationResult result = new ModificationResult(this );
0582: if (this .files.size() <= 1) {
0583: //long start = System.currentTimeMillis();
0584: final Source.Request request = currentRequest
0585: .getTaskToCancel();
0586: try {
0587: if (request != null) {
0588: request.task.cancel();
0589: }
0590: this .javacLock.lock();
0591: try {
0592: CompilationInfo currentInfo = null;
0593: synchronized (this ) {
0594: if (this .currentInfo != null
0595: && (this .flags & INVALID) == 0) {
0596: currentInfo = this .currentInfo;
0597: }
0598: }
0599: if (currentInfo == null) {
0600: currentInfo = createCurrentInfo(this ,
0601: this .files.isEmpty() ? null
0602: : this .files.iterator().next(),
0603: filterListener, null);
0604: synchronized (this ) {
0605: if (this .currentInfo == null
0606: || (this .flags & INVALID) != 0) {
0607: this .currentInfo = currentInfo;
0608: this .flags &= ~INVALID;
0609: } else {
0610: currentInfo = this .currentInfo;
0611: }
0612: }
0613: }
0614: assert currentInfo != null;
0615: WorkingCopy copy = new WorkingCopy(currentInfo);
0616: task.run(copy);
0617: List<Difference> diffs = copy.getChanges();
0618: if (diffs != null && diffs.size() > 0)
0619: result.diffs.put(currentInfo.getFileObject(),
0620: diffs);
0621: } catch (RuntimeException e) {
0622: throw e;
0623: } catch (Exception e) {
0624: IOException ioe = new IOException();
0625: ioe.initCause(e);
0626: throw ioe;
0627: } finally {
0628: this .javacLock.unlock();
0629: }
0630: } finally {
0631: currentRequest.cancelCompleted(request);
0632: }
0633: //TimesCollector.getDefault().reportTime(currentInfo.getFileObject(), "gsf-source-modification-task", //NOI18N
0634: // "Modification Task", System.currentTimeMillis() - start); //NOI18N
0635: } else {
0636: final Source.Request request = currentRequest
0637: .getTaskToCancel();
0638: try {
0639: if (request != null) {
0640: request.task.cancel();
0641: }
0642: this .javacLock.lock();
0643: try {
0644: ParserTaskImpl jt = null;
0645: FileObject activeFile = null;
0646: Iterator<FileObject> files = this .files.iterator();
0647: while (files.hasNext() || activeFile != null) {
0648: boolean restarted;
0649: if (activeFile == null) {
0650: activeFile = files.next();
0651: restarted = false;
0652: } else {
0653: restarted = true;
0654: }
0655: CompilationInfo ci = createCurrentInfo(this ,
0656: activeFile, filterListener, jt);
0657: WorkingCopy copy = new WorkingCopy(ci);
0658: task.run(copy);
0659: if (!ci.needsRestart) {
0660: jt = ci.getParserTask();
0661: //jt = ci.getParserTask();
0662: // Log.instance(jt.getContext()).nerrors = 0;
0663: List<Difference> diffs = copy.getChanges();
0664: if (diffs != null && diffs.size() > 0)
0665: result.diffs.put(ci.getFileObject(),
0666: diffs);
0667: activeFile = null;
0668: } else {
0669: jt = null;
0670: ci = null;
0671: System.gc();
0672: if (restarted) {
0673: throw new InsufficientMemoryException(
0674: activeFile);
0675: }
0676: }
0677: }
0678: } catch (RuntimeException e) {
0679: throw e;
0680: } catch (Exception e) {
0681: IOException ioe = new IOException();
0682: ioe.initCause(e);
0683: throw ioe;
0684: } finally {
0685: this .javacLock.unlock();
0686: }
0687: } finally {
0688: currentRequest.cancelCompleted(request);
0689: }
0690: }
0691: synchronized (this ) {
0692: this .flags |= INVALID;
0693: }
0694: return result;
0695: }
0696:
0697: /** Adds a task to given compilation phase. The tasks will run sequentially by
0698: * priorty after given phase is reached.
0699: * @see CancellableTask for information about implementation requirements
0700: * @task The task to run.
0701: * @phase In which phase should the task run
0702: * @priority Priority of the task.
0703: */
0704: void addPhaseCompletionTask(CancellableTask<CompilationInfo> task,
0705: Phase phase, Priority priority) throws IOException {
0706: if (task == null) {
0707: throw new IllegalArgumentException("Task cannot be null"); //NOI18N
0708: }
0709: if (phase == null || phase == Phase.MODIFIED) {
0710: throw new IllegalArgumentException(String.format(
0711: "The %s is not a legal value of phase", phase)); //NOI18N
0712: }
0713: if (priority == null) {
0714: throw new IllegalArgumentException(
0715: "The priority cannot be null"); //NOI18N
0716: }
0717: CompilationInfo currentInfo;
0718: synchronized (this ) {
0719: currentInfo = this .currentInfo;
0720: }
0721: if (currentInfo == null) {
0722: currentInfo = createCurrentInfo(this ,
0723: this .files.isEmpty() ? null : this .files.iterator()
0724: .next(), filterListener, null);
0725: }
0726: synchronized (this ) {
0727: if (this .currentInfo == null) {
0728: this .currentInfo = currentInfo;
0729: }
0730: }
0731: handleAddRequest(new Request(task, this , phase, priority, true));
0732: }
0733:
0734: /** Removes the task from the phase queue.
0735: * @task The task to remove.
0736: */
0737: void removePhaseCompletionTask(CancellableTask<CompilationInfo> task) {
0738: synchronized (Source.class) {
0739: toRemove.add(task);
0740: }
0741: }
0742:
0743: /**Rerun the task in case it was already run. Does nothing if the task was not already run.
0744: *
0745: * @task to reschedule
0746: */
0747: void rescheduleTask(CancellableTask<CompilationInfo> task) {
0748: synchronized (Source.class) {
0749: Source.Request request = this .currentRequest
0750: .getTaskToCancel(task);
0751: if (request == null) {
0752: out: for (Iterator<Collection<Request>> it = finishedRequests
0753: .values().iterator(); it.hasNext();) {
0754: Collection<Request> cr = it.next();
0755: for (Iterator<Request> it2 = cr.iterator(); it2
0756: .hasNext();) {
0757: Request fr = it2.next();
0758: if (task == fr.task) {
0759: it2.remove();
0760: Source.requests.add(fr);
0761: if (cr.size() == 0) {
0762: it.remove();
0763: }
0764: break out;
0765: }
0766: }
0767: }
0768: } else {
0769: currentRequest.cancelCompleted(request);
0770: }
0771: }
0772: }
0773:
0774: /**
0775: * Marks this {@link Source} as modified, causes that the cached information are
0776: * cleared and all the PhaseCompletionTasks are restarted.
0777: * The only client of this method should be the JavaDataObject or other DataObjects
0778: * providing the {@link Source}. If you call this method in another case you are
0779: * probably doing something incorrect.
0780: */
0781: void revalidate() {
0782: this .resetState(true, false);
0783: }
0784:
0785: /**
0786: * Returns the classpaths ({@link ClasspathInfo}) used by this
0787: * {@link Source}
0788: *
0789: *
0790: * @return {@link ClasspathInfo}, never returns null.
0791: */
0792: public ClasspathInfo getClasspathInfo() {
0793: return classpathInfo;
0794: }
0795:
0796: /**
0797: * Returns unmodifiable {@link Collection} of {@link FileObject}s used by this {@link Source}
0798: * @return the {@link FileObject}s
0799: */
0800: public Collection<FileObject> getFileObjects() {
0801: return files;
0802: }
0803:
0804: ParserTaskImpl createParserTask(
0805: /*final DiagnosticListener<? super SourceFileObject> diagnosticListener,*/CompilationInfo compilationInfo) {
0806: //assert diagnosticListener == null;
0807: Language language = compilationInfo.getLanguage();
0808: assert language != null;
0809: ParserTaskImpl javacTask = createParserTask(language,
0810: compilationInfo, getClasspathInfo(), /*diagnosticListener,*/
0811: sourceLevel, false);
0812: // Context context = javacTask.getContext();
0813: // Messager.preRegister(context, null);
0814: // ErrorHandlingJavadocEnter.preRegister(context);
0815: // JavadocMemberEnter.preRegister(context);
0816: // SourceUtils.JavaDocEnv.preRegister(context, getClasspathInfo());
0817: // Scanner.Factory.instance(context);
0818: // Builder2.instance(context).keepComments = true;
0819: return javacTask;
0820: }
0821:
0822: private static ParserTaskImpl createParserTask(Language language,
0823: final CompilationInfo currentInfo,
0824: final ClasspathInfo cpInfo, /*final DiagnosticListener<? super SourceFileObject> diagnosticListener,*/
0825: final String sourceLevel,
0826: final boolean backgroundCompilation) {
0827: ParserTaskImpl jti = new ParserTaskImpl(language);
0828:
0829: return jti;
0830: }
0831:
0832: /**
0833: * Not synchronized, only the CompilationJob's thread can call it!!!!
0834: *
0835: */
0836: static Phase moveToPhase(final Phase phase,
0837: final CompilationInfo currentInfo, final boolean cancellable)
0838: throws IOException {
0839: Phase currentPhase = currentInfo.getPhase();
0840: final boolean isMultiFiles = currentInfo.getSource().files
0841: .size() > 1;
0842: LowMemoryNotifier lm = null;
0843: LMListener lmListener = null;
0844: if (isMultiFiles) {
0845: lm = LowMemoryNotifier.getDefault();
0846: assert lm != null;
0847: lmListener = new LMListener();
0848: lm.addLowMemoryListener(lmListener);
0849: }
0850: try {
0851: if (lmListener != null
0852: && lmListener.lowMemory.getAndSet(false)) {
0853: currentInfo.needsRestart = true;
0854: return currentPhase;
0855: }
0856: if (currentPhase.compareTo(Phase.PARSED) < 0
0857: && phase.compareTo(Phase.PARSED) >= 0) {
0858: if (cancellable && currentRequest.isCanceled()) {
0859: //Keep the currentPhase unchanged, it may happen that an userActionTask
0860: //runnig after the phace completion task may still use it.
0861: return Phase.MODIFIED;
0862: }
0863: long start = System.currentTimeMillis();
0864:
0865: List<ParserFile> sourceFiles = new ArrayList<ParserFile>(
0866: 1);
0867: sourceFiles.add(new DefaultParserFile(currentInfo
0868: .getFileObject(), null, false));
0869: final FileObject bufferFo = currentInfo.getFileObject();
0870:
0871: final ParserResult[] resultHolder = new ParserResult[1];
0872: ParseListener listener = new ParseListener() {
0873: final List<Error> errors = new ArrayList<Error>();
0874:
0875: public void started(ParseEvent e) {
0876: errors.clear();
0877: }
0878:
0879: public void error(Error error) {
0880: errors.add(error);
0881: }
0882:
0883: public void exception(Exception exception) {
0884: Exceptions.printStackTrace(exception);
0885: }
0886:
0887: public void finished(ParseEvent e) {
0888: // TODO - check state
0889: if (e.getKind() == ParseEvent.Kind.PARSE) {
0890: ParserResult result = e.getResult();
0891: for (Error error : errors) {
0892: result.addError(error);
0893: }
0894: resultHolder[0] = result;
0895: }
0896: }
0897: };
0898:
0899: LanguageRegistry registry = LanguageRegistry
0900: .getInstance();
0901: String mimeType = currentInfo.getFileObject()
0902: .getMIMEType();
0903: List<Language> languages = registry
0904: .getApplicableLanguages(mimeType);
0905:
0906: for (Language language : languages) {
0907: EmbeddingModel model = registry.getEmbedding(
0908: language.getMimeType(), mimeType);
0909: assert language != null;
0910: Parser parser = language.getParser(); // Todo - call createParserTask here?
0911:
0912: if (parser != null) {
0913: if (model != null) {
0914: Document document;
0915: try {
0916: document = currentInfo.getDocument();
0917:
0918: if (document == null) {
0919: // Ensure document is forced open such that info.getDocument() will not yield null
0920: UiUtils.getDocument(currentInfo
0921: .getFileObject(), true);
0922: document = currentInfo
0923: .getDocument();
0924: }
0925: } catch (IOException ex) {
0926: Exceptions.printStackTrace(ex);
0927: return null;
0928: }
0929:
0930: if (document == null) {
0931: // TODO - log problem
0932: continue;
0933: }
0934:
0935: Collection<? extends TranslatedSource> translations = model
0936: .translate(document);
0937: for (TranslatedSource translatedSource : translations) {
0938: String buffer = translatedSource
0939: .getSource();
0940: SourceFileReader reader = new StringSourceFileReader(
0941: buffer, bufferFo);
0942: Parser.Job job = new Parser.Job(
0943: sourceFiles, listener, reader,
0944: translatedSource);
0945: parser.parseFiles(job);
0946: ParserResult result = resultHolder[0];
0947: result
0948: .setTranslatedSource(translatedSource);
0949: assert result != null;
0950: currentInfo.addEmbeddingResult(language
0951: .getMimeType(), result);
0952: }
0953: } else {
0954: String buffer = currentInfo.getText();
0955: SourceFileReader reader = new StringSourceFileReader(
0956: buffer, bufferFo);
0957: Parser.Job job = new Parser.Job(
0958: sourceFiles, listener, reader, null);
0959: parser.parseFiles(job);
0960: ParserResult result = resultHolder[0];
0961: assert result != null;
0962: currentInfo.addEmbeddingResult(language
0963: .getMimeType(), result);
0964: }
0965: }
0966: }
0967: currentPhase = Phase.PARSED;
0968: long end = System.currentTimeMillis();
0969: FileObject file = currentInfo.getFileObject();
0970: //TimesCollector.getDefault().reportReference(file, "compilationUnit", "[M] Compilation Unit", unit); //NOI18N
0971: logTime(file, currentPhase, (end - start));
0972: }
0973: if (lmListener != null
0974: && lmListener.lowMemory.getAndSet(false)) {
0975: currentInfo.needsRestart = true;
0976: return currentPhase;
0977: }
0978: if (currentPhase == Phase.PARSED
0979: && phase.compareTo(Phase.ELEMENTS_RESOLVED) >= 0) {
0980: if (cancellable && currentRequest.isCanceled()) {
0981: return Phase.MODIFIED;
0982: }
0983: long start = System.currentTimeMillis();
0984: // Noop right now - revisit when I add a parser which needs it (groovy?)
0985: //currentInfo.getParserTask().enter();
0986: currentPhase = Phase.ELEMENTS_RESOLVED;
0987: long end = System.currentTimeMillis();
0988: logTime(currentInfo.getFileObject(), currentPhase,
0989: (end - start));
0990: }
0991: if (lmListener != null
0992: && lmListener.lowMemory.getAndSet(false)) {
0993: currentInfo.needsRestart = true;
0994: return currentPhase;
0995: }
0996: if (currentPhase == Phase.ELEMENTS_RESOLVED
0997: && phase.compareTo(Phase.RESOLVED) >= 0) {
0998: if (cancellable && currentRequest.isCanceled()) {
0999: return Phase.MODIFIED;
1000: }
1001: long start = System.currentTimeMillis();
1002: // Noop right now - revisit when I add a parser which needs it (groovy?)
1003: //currentInfo.getParserTask().analyze();
1004: currentPhase = Phase.RESOLVED;
1005: long end = System.currentTimeMillis();
1006: logTime(currentInfo.getFileObject(), currentPhase,
1007: (end - start));
1008: }
1009: if (lmListener != null
1010: && lmListener.lowMemory.getAndSet(false)) {
1011: currentInfo.needsRestart = true;
1012: return currentPhase;
1013: }
1014: if (currentPhase == Phase.RESOLVED
1015: && phase.compareTo(Phase.UP_TO_DATE) >= 0) {
1016: currentPhase = Phase.UP_TO_DATE;
1017: }
1018: //} catch (Error abort) { // Abort in com.sun.tools is not here
1019: // currentPhase = Phase.UP_TO_DATE;
1020: // } catch (IOException ex) {
1021: // dumpSource(currentInfo, ex);
1022: // throw ex;
1023: } catch (RuntimeException ex) {
1024: dumpSource(currentInfo, ex);
1025: throw ex;
1026: } catch (java.lang.Error ex) {
1027: dumpSource(currentInfo, ex);
1028: throw ex;
1029: } finally {
1030: if (isMultiFiles) {
1031: assert lm != null;
1032: assert lmListener != null;
1033: lm.removeLowMemoryListener(lmListener);
1034: }
1035: currentInfo.setPhase(currentPhase);
1036: }
1037: return currentPhase;
1038: }
1039:
1040: static void logTime(FileObject source, Phase phase, long time) {
1041: assert source != null && phase != null;
1042: String key = phase2Key.get(phase);
1043: String message = phase2Message.get(phase);
1044: assert key != null && message != null;
1045: //TimesCollector.getDefault().reportTime (source,key,message,time);
1046: }
1047:
1048: private final RequestProcessor.Task resetTask = RequestProcessor
1049: .getDefault().create(new Runnable() {
1050: public void run() {
1051: resetStateImpl();
1052: }
1053: });
1054:
1055: private void resetState(boolean invalidate, boolean updateIndex) {
1056: boolean invalid;
1057: synchronized (this ) {
1058: invalid = (this .flags & INVALID) != 0;
1059: this .flags |= CHANGE_EXPECTED;
1060: if (invalidate) {
1061: this .flags |= (INVALID | RESCHEDULE_FINISHED_TASKS);
1062: }
1063: if (updateIndex) {
1064: this .flags |= UPDATE_INDEX;
1065: }
1066: }
1067: if (updateIndex && !invalid) {
1068: //First change set the index as dirty
1069: updateIndex();
1070: }
1071: Request r = currentRequest.getTaskToCancel(this );
1072: try {
1073: if (r != null) {
1074: r.task.cancel();
1075: }
1076: } finally {
1077: currentRequest.cancelCompleted(r);
1078: }
1079: resetTask.schedule(REPARSE_DELAY);
1080: }
1081:
1082: /**
1083: * Not synchronized, only sets the atomic state and clears the listeners
1084: *
1085: */
1086: private void resetStateImpl() {
1087: synchronized (Source.class) {
1088: boolean reschedule, updateIndex;
1089: synchronized (this ) {
1090: reschedule = (this .flags & RESCHEDULE_FINISHED_TASKS) != 0;
1091: updateIndex = (this .flags & UPDATE_INDEX) != 0;
1092: this .flags &= ~(RESCHEDULE_FINISHED_TASKS
1093: | CHANGE_EXPECTED | UPDATE_INDEX);
1094: }
1095: if (updateIndex) {
1096: //Last change set the index as dirty
1097: updateIndex();
1098: }
1099: Collection<Request> cr;
1100: if (reschedule) {
1101: if ((cr = Source.finishedRequests.remove(this )) != null
1102: && cr.size() > 0) {
1103: Source.requests.addAll(cr);
1104: }
1105: }
1106: if ((cr = Source.waitingRequests.remove(this )) != null
1107: && cr.size() > 0) {
1108: Source.requests.addAll(cr);
1109: }
1110: }
1111: }
1112:
1113: private void updateIndex() {
1114: if (this .rootFo != null) {
1115: try {
1116: for (Language language : LanguageRegistry.getInstance()) {
1117: if (language.getIndexer() != null) {
1118: ClassIndexImpl ciImpl = ClassIndexManager.get(
1119: language).getUsagesQuery(
1120: this .rootFo.getURL());
1121: if (ciImpl != null) {
1122: ciImpl.setDirty(this );
1123: }
1124: }
1125: }
1126: } catch (IOException ioe) {
1127: Exceptions.printStackTrace(ioe);
1128: }
1129: }
1130: }
1131:
1132: /** For test framework only */
1133: public void testUpdateIndex() {
1134: updateIndex();
1135: }
1136:
1137: private void assignDocumentListener(FileObject fo)
1138: throws IOException {
1139: DataObject od = DataObject.find(fo);
1140: EditorCookie.Observable ec = (EditorCookie.Observable) od
1141: .getCookie(EditorCookie.Observable.class);
1142: if (ec != null) {
1143: this .listener = new DocListener(ec);
1144: } else {
1145: Logger.getLogger("global").log(
1146: Level.WARNING,
1147: String.format(
1148: "File: %s has no EditorCookie.Observable",
1149: FileUtil.getFileDisplayName(fo))); //NOI18N
1150: }
1151: }
1152:
1153: private static class Request {
1154: private final CancellableTask<? extends CompilationInfo> task;
1155: private final Source javaSource; //XXX: Maybe week, depends on the semantics
1156: private final Phase phase;
1157: private final Priority priority;
1158: private final boolean reschedule;
1159:
1160: public Request(
1161: final CancellableTask<? extends CompilationInfo> task,
1162: final Source javaSource, final Phase phase,
1163: final Priority priority, final boolean reschedule) {
1164: assert task != null;
1165: this .task = task;
1166: this .javaSource = javaSource;
1167: this .phase = phase;
1168: this .priority = priority;
1169: this .reschedule = reschedule;
1170: }
1171:
1172: public @Override
1173: String toString() {
1174: if (reschedule) {
1175: return String
1176: .format(
1177: "Periodic request for phase: %s with priority: %s to perform: %s",
1178: phase.name(), priority, task.toString()); //NOI18N
1179: } else {
1180: return String
1181: .format(
1182: "One time request for phase: %s with priority: %d to perform: %s",
1183: phase != null ? phase.name() : "<null>",
1184: priority, task.toString()); //NOI18N
1185: }
1186: }
1187:
1188: public @Override
1189: int hashCode() {
1190: return this .priority.ordinal();
1191: }
1192:
1193: public @Override
1194: boolean equals(Object other) {
1195: if (other instanceof Request) {
1196: Request otherRequest = (Request) other;
1197: return priority == otherRequest.priority
1198: && reschedule == otherRequest.reschedule
1199: && phase.equals(otherRequest.phase)
1200: && task.equals(otherRequest.task);
1201: } else {
1202: return false;
1203: }
1204: }
1205: }
1206:
1207: private static class RequestComparator implements
1208: Comparator<Request> {
1209: public int compare(Request r1, Request r2) {
1210: assert r1 != null && r2 != null;
1211: return r1.priority.compareTo(r2.priority);
1212: }
1213: }
1214:
1215: private static class CompilationJob implements Runnable {
1216:
1217: @SuppressWarnings("unchecked")
1218: //NOI18N
1219: public void run() {
1220: try {
1221: while (true) {
1222: try {
1223: synchronized (Source.class) {
1224: //Clean up toRemove tasks
1225: if (!toRemove.isEmpty()) {
1226: for (Iterator<Collection<Request>> it = finishedRequests
1227: .values().iterator(); it
1228: .hasNext();) {
1229: Collection<Request> cr = it.next();
1230: for (Iterator<Request> it2 = cr
1231: .iterator(); it2.hasNext();) {
1232: Request fr = it2.next();
1233: if (toRemove.remove(fr.task)) {
1234: it2.remove();
1235: }
1236: }
1237: if (cr.size() == 0) {
1238: it.remove();
1239: }
1240: }
1241: }
1242: }
1243: Request r = Source.requests.poll(2,
1244: TimeUnit.SECONDS);
1245: if (r != null) {
1246: currentRequest.setCurrentTask(r);
1247: try {
1248: Source js = r.javaSource;
1249: if (js == null) {
1250: assert r.phase == null;
1251: assert r.reschedule == false;
1252: javacLock.lock();
1253: try {
1254: r.task.run(null);
1255: } catch (RuntimeException re) {
1256: Exceptions.printStackTrace(re);
1257: } finally {
1258: javacLock.unlock();
1259: }
1260: } else {
1261: assert js.files.size() <= 1;
1262: boolean jsInvalid;
1263: CompilationInfo ci;
1264: synchronized (Source.class) {
1265: //jl:what does this comment mean?
1266: //Not only the finishedRequests for the current request.javaSource should be cleaned,
1267: //it will cause a starvation
1268: if (toRemove.remove(r.task)) {
1269: continue;
1270: }
1271: synchronized (js) {
1272: boolean changeExpected = (js.flags & CHANGE_EXPECTED) != 0;
1273: if (changeExpected) {
1274: //Skip the task, another invalidation is comming
1275: Collection<Request> rc = Source.waitingRequests
1276: .get(r.javaSource);
1277: if (rc == null) {
1278: rc = new LinkedList<Request>();
1279: Source.waitingRequests
1280: .put(
1281: r.javaSource,
1282: rc);
1283: }
1284: rc.add(r);
1285: continue;
1286: }
1287: jsInvalid = (js.flags & INVALID) != 0;
1288: ci = js.currentInfo;
1289: }
1290: }
1291: try {
1292: //createCurrentInfo has to be out of synchronized block, it aquires an editor lock
1293: if (jsInvalid) {
1294: ci = createCurrentInfo(
1295: js,
1296: js.files.isEmpty() ? null
1297: : js.files
1298: .iterator()
1299: .next(),
1300: js.filterListener,
1301: null);
1302: synchronized (js) {
1303: if ((js.flags & INVALID) != 0) {
1304: js.currentInfo = ci;
1305: js.flags &= ~INVALID;
1306: } else {
1307: ci = js.currentInfo;
1308: }
1309: }
1310: }
1311: assert ci != null;
1312: javacLock.lock();
1313: try {
1314: final Phase phase = Source
1315: .moveToPhase(
1316: r.phase,
1317: ci, true);
1318: boolean shouldCall = phase
1319: .compareTo(r.phase) >= 0;
1320: if (shouldCall) {
1321: synchronized (js) {
1322: shouldCall &= (js.flags & INVALID) == 0;
1323: }
1324: if (shouldCall) {
1325: //The state (or greater) was reached and document was not modified during moveToPhase
1326: try {
1327: final long startTime = System
1328: .currentTimeMillis();
1329: ((CancellableTask<CompilationInfo>) r.task)
1330: .run(ci); //XXX: How to do it in save way?
1331: final long endTime = System
1332: .currentTimeMillis();
1333: if (reportSlowTasks) {
1334: if ((endTime - startTime) > SLOW_TASK_LIMIT) {
1335: Logger
1336: .getLogger(
1337: "global")
1338: .log(
1339: Level.INFO,
1340: String
1341: .format(
1342: "Source executed a slow task: %s in %d ms.", //NOI18N
1343: r.task
1344: .getClass()
1345: .toString(),
1346: (endTime - startTime)));
1347: }
1348: final long cancelTime = currentRequest
1349: .getCancelTime();
1350: if (cancelTime >= startTime
1351: && (endTime - cancelTime) > SLOW_CANCEL_LIMIT) {
1352: Logger
1353: .getLogger(
1354: "global")
1355: .log(
1356: Level.INFO,
1357: String
1358: .format(
1359: "Task: %s ignored cancel for %d ms.", //NOI18N
1360: r.task
1361: .getClass()
1362: .toString(),
1363: (endTime - cancelTime)));
1364: }
1365: }
1366: } catch (RuntimeException re) {
1367: Exceptions
1368: .printStackTrace(re);
1369: }
1370: }
1371: }
1372: } finally {
1373: javacLock.unlock();
1374: }
1375:
1376: if (r.reschedule) {
1377: synchronized (Source.class) {
1378: boolean canceled = currentRequest
1379: .setCurrentTask(null);
1380: synchronized (js) {
1381: if ((js.flags & INVALID) != 0
1382: || canceled) {
1383: //The Source was changed or canceled rechedule it now
1384: Source.requests
1385: .add(r);
1386: } else {
1387: //Up to date Source add it to the finishedRequests
1388: Collection<Request> rc = Source.finishedRequests
1389: .get(r.javaSource);
1390: if (rc == null) {
1391: rc = new LinkedList<Request>();
1392: Source.finishedRequests
1393: .put(
1394: r.javaSource,
1395: rc);
1396: }
1397: rc.add(r);
1398: }
1399: }
1400: }
1401: }
1402: } catch (final IOException invalidFile) {
1403: //Ideally the requests should be removed by JavaSourceTaskFactory and task should be put to finishedRequests,
1404: //but the reality is different, the task cannot be put to finished request because of possible memory leak
1405: }
1406: }
1407: } finally {
1408: currentRequest.setCurrentTask(null);
1409: }
1410: }
1411: } catch (Throwable e) {
1412: if (e instanceof InterruptedException) {
1413: throw (InterruptedException) e;
1414: } else if (e instanceof ThreadDeath) {
1415: throw (ThreadDeath) e;
1416: } else {
1417: Exceptions.printStackTrace(e);
1418: }
1419: }
1420: }
1421: } catch (InterruptedException ie) {
1422: ie.printStackTrace();
1423: // stop the service.
1424: }
1425: }
1426: }
1427:
1428: private class DocListener implements DocumentListener,
1429: PropertyChangeListener, ChangeListener {
1430:
1431: private EditorCookie.Observable ec;
1432: private DocumentListener docListener;
1433:
1434: public DocListener(EditorCookie.Observable ec) {
1435: assert ec != null;
1436: this .ec = ec;
1437: this .ec
1438: .addPropertyChangeListener((PropertyChangeListener) WeakListeners
1439: .propertyChange(this , this .ec));
1440: Document doc = ec.getDocument();
1441: if (doc != null) {
1442: doc.addDocumentListener(docListener = WeakListeners
1443: .create(DocumentListener.class, this , doc));
1444: }
1445: }
1446:
1447: public void insertUpdate(DocumentEvent e) {
1448: //Has to reset cache asynchronously
1449: //the callback cannot be in synchronized section
1450: //since NbDocument.runAtomic fires under lock
1451: Source.this .resetState(true, true);
1452: }
1453:
1454: public void removeUpdate(DocumentEvent e) {
1455: //Has to reset cache asynchronously
1456: //the callback cannot be in synchronized section
1457: //since NbDocument.runAtomic fires under lock
1458: Source.this .resetState(true, true);
1459: }
1460:
1461: public void changedUpdate(DocumentEvent e) {
1462: }
1463:
1464: public void propertyChange(PropertyChangeEvent evt) {
1465: if (EditorCookie.Observable.PROP_DOCUMENT.equals(evt
1466: .getPropertyName())) {
1467: Object old = evt.getOldValue();
1468: if (old instanceof Document && docListener != null) {
1469: ((Document) old)
1470: .removeDocumentListener(docListener);
1471: docListener = null;
1472: }
1473: Document doc = ec.getDocument();
1474: if (doc != null) {
1475: doc.addDocumentListener(docListener = WeakListeners
1476: .create(DocumentListener.class, this , doc));
1477: resetState(true, false);
1478: }
1479: }
1480: }
1481:
1482: public void stateChanged(ChangeEvent e) {
1483: Source.this .resetState(true, false);
1484: }
1485:
1486: }
1487:
1488: private static class EditorRegistryListener implements
1489: ChangeListener, CaretListener {
1490:
1491: private JTextComponent lastEditor;
1492:
1493: public EditorRegistryListener() {
1494: Registry.addChangeListener(this );
1495: }
1496:
1497: public void stateChanged(ChangeEvent event) {
1498: final JTextComponent editor = Registry
1499: .getMostActiveComponent();
1500: if (lastEditor != editor) {
1501: if (lastEditor != null) {
1502: lastEditor.removeCaretListener(this );
1503: }
1504: lastEditor = editor;
1505: if (lastEditor != null) {
1506: lastEditor.addCaretListener(this );
1507: }
1508: }
1509: }
1510:
1511: public void caretUpdate(CaretEvent event) {
1512: if (lastEditor != null) {
1513: Document doc = lastEditor.getDocument();
1514: if (doc != null) {
1515: Source js = forDocument(doc);
1516: if (js != null) {
1517: js.resetState(false, false);
1518: }
1519: }
1520: }
1521: }
1522: }
1523:
1524: private class FileChangeListenerImpl extends FileChangeAdapter {
1525:
1526: public @Override
1527: void fileChanged(final FileEvent fe) {
1528: Source.this .resetState(true, false);
1529: }
1530:
1531: public @Override
1532: void fileRenamed(FileRenameEvent fe) {
1533: Source.this .resetState(true, false);
1534: }
1535: }
1536:
1537: private final class DataObjectListener implements
1538: PropertyChangeListener {
1539:
1540: private DataObject dobj;
1541: private final FileObject fobj;
1542: private PropertyChangeListener wlistener;
1543:
1544: public DataObjectListener(FileObject fo)
1545: throws DataObjectNotFoundException {
1546: this .fobj = fo;
1547: this .dobj = DataObject.find(fo);
1548: wlistener = WeakListeners.propertyChange(this , dobj);
1549: this .dobj.addPropertyChangeListener(wlistener);
1550: }
1551:
1552: public void propertyChange(PropertyChangeEvent pce) {
1553: DataObject invalidDO = (DataObject) pce.getSource();
1554: if (invalidDO != dobj)
1555: return;
1556: if (DataObject.PROP_VALID.equals(pce.getPropertyName())) {
1557: handleInvalidDataObject(invalidDO);
1558: } else if (pce.getPropertyName() == null && !dobj.isValid()) {
1559: handleInvalidDataObject(invalidDO);
1560: }
1561: }
1562:
1563: private void handleInvalidDataObject(DataObject invalidDO) {
1564: invalidDO.removePropertyChangeListener(wlistener);
1565: if (fobj.isValid()) {
1566: // file object still exists try to find new data object
1567: try {
1568: dobj = DataObject.find(fobj);
1569: dobj.addPropertyChangeListener(wlistener);
1570: assignDocumentListener(fobj);
1571: resetState(true, true);
1572: } catch (IOException ex) {
1573: // should not occur
1574: Logger.getLogger(Source.class.getName()).log(
1575: Level.SEVERE, ex.getMessage(), ex);
1576: }
1577: }
1578: }
1579:
1580: }
1581:
1582: private static CompilationInfo createCurrentInfo(final Source js,
1583: final FileObject fo,
1584: final Object/*FilterListener*/filterListener,
1585: final ParserTaskImpl javac) throws IOException {
1586: if (js.sourceLevel == null && fo != null)
1587: js.sourceLevel = SourceLevelQuery.getSourceLevel(fo);
1588: if (js.sourceLevel == null)
1589: js.sourceLevel = JavaPlatformManager.getDefault()
1590: .getDefaultPlatform().getSpecification()
1591: .getVersion().toString();
1592: CompilationInfo info = new CompilationInfo(js, fo, javac);
1593:
1594: //TimesCollector.getDefault().reportReference(fo, CompilationInfo.class.toString(), "[M] CompilationInfo", info); //NOI18N
1595: return info;
1596: }
1597:
1598: private static void handleAddRequest(final Request nr) {
1599: assert nr != null;
1600: requests.add(nr);
1601: Source.Request request = currentRequest
1602: .getTaskToCancel(nr.priority);
1603: try {
1604: if (request != null) {
1605: request.task.cancel();
1606: }
1607: } finally {
1608: currentRequest.cancelCompleted(request);
1609: }
1610: }
1611:
1612: private static class SingleThreadFactory implements ThreadFactory {
1613:
1614: private Thread t;
1615:
1616: public Thread newThread(Runnable r) {
1617: assert this .t == null;
1618: this .t = new Thread(r, "GSF Source Worker Thread"); //NOI18N
1619: return this .t;
1620: }
1621:
1622: public boolean isDispatchThread(Thread t) {
1623: assert t != null;
1624: return this .t == t;
1625: }
1626: }
1627:
1628: private static class JavaSourceAccessorImpl extends SourceAccessor {
1629: private StackTraceElement[] javacLockedStackTrace;
1630:
1631: protected @Override
1632: void runSpecialTaskImpl(CancellableTask<CompilationInfo> task,
1633: Priority priority) {
1634: handleAddRequest(new Request(task, null, null, priority,
1635: false));
1636: }
1637:
1638: @Override
1639: public ParserTaskImpl createParserTask(Language language,
1640: ClasspathInfo cpInfo, /*DiagnosticListener<? super SourceFileObject> diagnosticListener,*/
1641: String sourceLevel) {
1642: if (sourceLevel == null)
1643: sourceLevel = JavaPlatformManager.getDefault()
1644: .getDefaultPlatform().getSpecification()
1645: .getVersion().toString();
1646: // return Source.createParserTask(cpInfo, diagnosticListener, sourceLevel, true);
1647: // throw new RuntimeException("Not yet implemented - I need the CompilationInfo here so I can pass in the language etc.");
1648: boolean backgroundCompilation = true; // Is this called from anywhere else?
1649: return Source.createParserTask(language, null, cpInfo,
1650: sourceLevel, backgroundCompilation);
1651: }
1652:
1653: @Override
1654: public ParserTaskImpl/*JavacTaskImpl*/getParserTask(
1655: final CompilationInfo compilationInfo) {
1656: assert compilationInfo != null;
1657: return compilationInfo.getParserTask();
1658: }
1659:
1660: @Override
1661: public CompilationInfo getCurrentCompilationInfo(
1662: final Source js, final Phase phase) throws IOException {
1663: assert js != null;
1664: assert isDispatchThread();
1665: CompilationInfo info = null;
1666: synchronized (js) {
1667: if ((js.flags & INVALID) == 0) {
1668: info = js.currentInfo;
1669: }
1670: }
1671: if (info == null) {
1672: return null;
1673: }
1674: Phase currentPhase = moveToPhase(phase, info, true);
1675: return currentPhase.compareTo(phase) < 0 ? null : info;
1676: }
1677:
1678: @Override
1679: public void revalidate(Source js) {
1680: js.revalidate();
1681: }
1682:
1683: @Override
1684: public boolean isDispatchThread() {
1685: return factory.isDispatchThread(Thread.currentThread());
1686: }
1687:
1688: @Override
1689: public void lockParser() {
1690: javacLock.lock();
1691: try {
1692: this .javacLockedStackTrace = Thread.currentThread()
1693: .getStackTrace();
1694: } catch (RuntimeException e) {
1695: //Not important, thrown by logging code
1696: }
1697: }
1698:
1699: @Override
1700: public void unlockParser() {
1701: try {
1702: this .javacLockedStackTrace = null;
1703: } finally {
1704: javacLock.unlock();
1705: }
1706: }
1707:
1708: @Override
1709: public boolean isParserLocked() {
1710: return javacLock.isLocked();
1711: }
1712: }
1713:
1714: private static class CurrentRequestReference {
1715:
1716: private Source.Request reference;
1717: private Source.Request canceledReference;
1718: private long cancelTime;
1719: private boolean canceled;
1720:
1721: public CurrentRequestReference() {
1722: }
1723:
1724: public boolean setCurrentTask(Source.Request reference)
1725: throws InterruptedException {
1726: boolean result = false;
1727: synchronized (this ) {
1728: while (this .canceledReference != null) {
1729: this .wait();
1730: }
1731: result = this .canceled;
1732: this .canceled = false;
1733: this .cancelTime = 0;
1734: this .reference = reference;
1735: }
1736: return result;
1737: }
1738:
1739: public Source.Request getTaskToCancel(final Priority priority) {
1740: Source.Request request = null;
1741: if (!factory.isDispatchThread(Thread.currentThread())) {
1742: synchronized (this ) {
1743: if (this .reference != null
1744: && priority
1745: .compareTo(this .reference.priority) < 0) {
1746: assert this .canceledReference == null;
1747: request = this .reference;
1748: this .canceledReference = request;
1749: this .reference = null;
1750: this .canceled = true;
1751: if (reportSlowTasks) {
1752: cancelTime = System.currentTimeMillis();
1753: }
1754: }
1755: }
1756: }
1757: return request;
1758: }
1759:
1760: public Source.Request getTaskToCancel(final Source js) {
1761: Source.Request request = null;
1762: if (!factory.isDispatchThread(Thread.currentThread())) {
1763: synchronized (this ) {
1764: if (this .reference != null
1765: && js.equals(this .reference.javaSource)) {
1766: assert this .canceledReference == null;
1767: request = this .reference;
1768: this .canceledReference = request;
1769: this .reference = null;
1770: this .canceled = true;
1771: if (reportSlowTasks) {
1772: cancelTime = System.currentTimeMillis();
1773: }
1774: }
1775: }
1776: }
1777: return request;
1778: }
1779:
1780: public Source.Request getTaskToCancel(final CancellableTask task) {
1781: Source.Request request = null;
1782: if (!factory.isDispatchThread(Thread.currentThread())) {
1783: synchronized (this ) {
1784: if (this .reference != null
1785: && task == this .reference.task) {
1786: assert this .canceledReference == null;
1787: request = this .reference;
1788: this .canceledReference = request;
1789: this .reference = null;
1790: this .canceled = true;
1791: }
1792: }
1793: }
1794: return request;
1795: }
1796:
1797: public Source.Request getTaskToCancel() {
1798: Source.Request request = null;
1799: if (!factory.isDispatchThread(Thread.currentThread())) {
1800: synchronized (this ) {
1801: request = this .reference;
1802: if (request != null) {
1803: assert this .canceledReference == null;
1804: this .canceledReference = request;
1805: this .reference = null;
1806: this .canceled = true;
1807: if (reportSlowTasks) {
1808: cancelTime = System.currentTimeMillis();
1809: }
1810: }
1811: }
1812: }
1813: return request;
1814: }
1815:
1816: public synchronized boolean isCanceled() {
1817: return this .canceled;
1818: }
1819:
1820: public synchronized long getCancelTime() {
1821: return this .cancelTime;
1822: }
1823:
1824: public void cancelCompleted(final Source.Request request) {
1825: if (request != null) {
1826: synchronized (this ) {
1827: assert request == this .canceledReference;
1828: this .canceledReference = null;
1829: this .notify();
1830: }
1831: }
1832: }
1833: }
1834:
1835: private static class LMListener implements LowMemoryListener {
1836: private AtomicBoolean lowMemory = new AtomicBoolean(false);
1837:
1838: public void lowMemory(LowMemoryEvent event) {
1839: lowMemory.set(true);
1840: }
1841: }
1842:
1843: /**
1844: *Ugly and slow, called only when -ea
1845: *
1846: */
1847: private static boolean holdsDocumentWriteLock(
1848: Iterable<FileObject> files) {
1849: final Class<AbstractDocument> docClass = AbstractDocument.class;
1850: try {
1851: final Method method = docClass
1852: .getDeclaredMethod("getCurrentWriter"); //NOI18N
1853: method.setAccessible(true);
1854: final Thread currentThread = Thread.currentThread();
1855: for (FileObject fo : files) {
1856: try {
1857: final DataObject dobj = DataObject.find(fo);
1858: final EditorCookie ec = (EditorCookie) dobj
1859: .getCookie(EditorCookie.class);
1860: if (ec != null) {
1861: final StyledDocument doc = ec.getDocument();
1862: if (doc instanceof AbstractDocument) {
1863: Object result = method.invoke(doc);
1864: if (result == currentThread) {
1865: return true;
1866: }
1867: }
1868: }
1869: } catch (Exception e) {
1870: Exceptions.printStackTrace(e);
1871: }
1872: }
1873: } catch (NoSuchMethodException e) {
1874: Exceptions.printStackTrace(e);
1875: }
1876: return false;
1877: }
1878:
1879: private static final int MAX_DUMPS = 255;
1880:
1881: /**
1882: * Dumps the source code to the file. Used for parser debugging. Only a limited number
1883: * of dump files is used. If the last file exists, this method doesn't dump anything.
1884: *
1885: * @param info CompilationInfo for which the error occurred.
1886: * @param exc exception to write to the end of dump file
1887: */
1888: private static void dumpSource(CompilationInfo info, Throwable exc) {
1889: String dumpDir = System.getProperty("netbeans.user")
1890: + "/var/log/"; //NOI18N
1891: String src = info.getText();
1892: FileObject file = info.getFileObject();
1893: String fileName = FileUtil.getFileDisplayName(info
1894: .getFileObject());
1895: String origName = file.getName();
1896: File f = new File(dumpDir + origName + ".dump"); // NOI18N
1897: boolean dumpSucceeded = false;
1898: int i = 1;
1899: while (i < MAX_DUMPS) {
1900: if (!f.exists())
1901: break;
1902: f = new File(dumpDir + origName + '_' + i + ".dump"); // NOI18N
1903: i++;
1904: }
1905: if (!f.exists()) {
1906: try {
1907: OutputStream os = new FileOutputStream(f);
1908: PrintWriter writer = new PrintWriter(
1909: new OutputStreamWriter(os, "UTF-8")); // NOI18N
1910: try {
1911: writer.println(src);
1912: writer
1913: .println("----- Classpath: ---------------------------------------------"); // NOI18N
1914:
1915: final ClassPath bootPath = info.getClasspathInfo()
1916: .getClassPath(ClasspathInfo.PathKind.BOOT);
1917: final ClassPath classPath = info.getClasspathInfo()
1918: .getClassPath(
1919: ClasspathInfo.PathKind.COMPILE);
1920: final ClassPath sourcePath = info
1921: .getClasspathInfo().getClassPath(
1922: ClasspathInfo.PathKind.SOURCE);
1923:
1924: writer.println("bootPath: "
1925: + (bootPath != null ? bootPath.toString()
1926: : "null"));
1927: writer.println("classPath: "
1928: + (classPath != null ? classPath.toString()
1929: : "null"));
1930: writer.println("sourcePath: "
1931: + (sourcePath != null ? sourcePath
1932: .toString() : "null"));
1933:
1934: writer
1935: .println("----- Original exception ---------------------------------------------"); // NOI18N
1936: exc.printStackTrace(writer);
1937: } finally {
1938: writer.close();
1939: dumpSucceeded = true;
1940: }
1941: } catch (IOException ioe) {
1942: Logger.getLogger("global").log(Level.INFO,
1943: "Error when writing parser dump file!", ioe); // NOI18N
1944: }
1945: }
1946: if (dumpSucceeded) {
1947: Throwable t = Exceptions
1948: .attachMessage(
1949: exc,
1950: "An error occurred during parsing of \'"
1951: + fileName
1952: + "\'. Please report a bug against ruby and attach dump file '" // NOI18N
1953: + f.getAbsolutePath() + "'."); // NOI18N
1954: Exceptions.printStackTrace(t);
1955: } else {
1956: Logger
1957: .getLogger("global")
1958: .log(
1959: Level.WARNING,
1960: "Dump could not be written. Either dump file could not "
1961: + // NOI18N
1962: "be created or all dump files were already used. Please "
1963: + // NOI18N
1964: "check that you have write permission to '"
1965: + dumpDir + "' and " + // NOI18N
1966: "clean all *.dump files in that directory."); // NOI18N
1967: }
1968: }
1969: }
|