/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.tools.wst.ui;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.context.AnalysisContext;
import com.google.dart.engine.context.AnalysisErrorInfo;
import com.google.dart.engine.context.AnalysisException;
import com.google.dart.engine.context.ChangeSet;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.error.AnalysisError;
import com.google.dart.engine.source.FileBasedSource;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.utilities.source.LineInfo;
import com.google.dart.tools.core.DartCore;
import com.google.dart.tools.core.DartCoreDebug;
import com.google.dart.tools.core.analysis.model.Project;
import com.google.dart.tools.core.internal.builder.AnalysisMarkerManager;
import com.google.dart.tools.core.internal.util.ResourceUtil;
import com.google.dart.tools.deploy.Activator;
import com.google.dart.tools.internal.corext.refactoring.util.ReflectionUtils;
import org.eclipse.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBuffer;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IRegion;
import org.eclipse.wst.sse.ui.internal.reconcile.validator.ISourceValidator;
import org.eclipse.wst.validation.internal.core.ValidationException;
import org.eclipse.wst.validation.internal.provisional.core.IReporter;
import org.eclipse.wst.validation.internal.provisional.core.IValidationContext;
import org.eclipse.wst.validation.internal.provisional.core.IValidator;
import java.io.File;
/**
* Attempt to bridge the impedance mismatch between Dart's reconciler and that of WST.
* <p>
* Currently, only two operations are supported.
* <ul>
* <li>Semantic highlighting requires a parsed AST but works better with a resolved AST. Since
* highlighting runs synchronously in the UI thread the parsed AST is computed when the resolved AST
* is unavailable.
* <li>Code completion requires a resolved AST. That is obtained by valiate(), which runs in a
* background thread.
* </ul>
* Other operations that work with the resolved AST could be supported, as long as no changes are
* required to compute the AST. Parsing and resolving can interfere with each other, leaving fake
* sources in the analysis context (which breaks refactoring). It isn't practical to synchronize the
* operations since parsing runs on the UI thread, so separate sources are used for each. Obviously,
* this is too fragile to be used for much more that it currently is.
* <p>
* This whole scheme should be replaced ASAP, after the analysis engine properly records analysis
* artifacts for HTML files.
* <p>
* NB: Although Dartium is currently restricted to a single script in HTML, polymer and angular both
* support Dart references within {{...}} data binding expressions, and those will have to be
* handled, too. In particular, Rename will have to work. Eventually, Dartium is expected to support
* multiple script tags.
*/
@SuppressWarnings("restriction")
public class EmbeddedDartReconcilerHook implements ISourceValidator, IValidator {
private File file;
private IResource resource;
private Project dartProject;
private CompilationUnit parsedUnit;
private CompilationUnit resolvedUnit;
private IDocument document;
private int partOffset;
private int partLength;
private IDocumentListener documentListener = new IDocumentListener() {
@Override
public void documentAboutToBeChanged(DocumentEvent event) {
reset();
}
@Override
public void documentChanged(DocumentEvent event) {
}
};
public EmbeddedDartReconcilerHook() {
}
@Override
public void cleanup(IReporter reporter) {
// IValidator -- not needed
}
@Override
public void connect(IDocument document) {
// ISourceValidator
this.document = document;
ITextFileBufferManager fileManager = FileBuffers.getTextFileBufferManager();
ITextFileBuffer fileBuffer = fileManager.getTextFileBuffer(document);
try {
file = fileBuffer.getFileStore().toLocalFile(0, null);
} catch (CoreException ex) {
Activator.logError(ex);
return;
}
IResource resource = ResourceUtil.getResource(file);
if (resource == null) {
// TODO(messick) How to handle html files in packages?
this.document = null;
this.file = null;
this.dartProject = null;
this.resource = null;
reset();
return;
}
IProject project = resource.getProject();
if (!DartCoreDebug.ENABLE_ANALYSIS_SERVER) {
dartProject = DartCore.getProjectManager().getProject(project);
}
DartReconcilerManager.getInstance().reconcileWith(document, this);
document.addPrenotifiedDocumentListener(documentListener);
this.resource = resource;
}
@Override
public void disconnect(IDocument document) {
// ISourceValidator
document.removePrenotifiedDocumentListener(documentListener);
DartReconcilerManager.getInstance().reconcileWith(document, null);
this.document = null;
this.file = null;
this.dartProject = null;
this.resource = null;
reset();
}
public IDocument getDocument() {
return document;
}
public AnalysisContext getInputAnalysisContext() {
// DartReconcilingEditor
if (resource == null || dartProject == null) {
return null;
}
return dartProject.getContext(resource);
}
public Project getInputProject() {
// DartReconcilingEditor
return dartProject;
}
public Source getInputSource() {
// DartReconcilingEditor
return null;
}
public CompilationUnit getParsedUnit(int offset, int length, IDocument document) {
if (isParsed(offset, length)) {
return parsedUnit;
}
if (this.document == null) {
connect(document);
}
return parseSource(offset, length);
}
public CompilationUnit getResolvedUnit(int offset, int length, IDocument document) {
if (isResolved(offset, length)) {
return resolvedUnit;
}
if (this.document == null) {
connect(document);
}
return resolveParsedUnit(offset, length);
}
public boolean isParsed(int offset, int length) {
return parsedUnit != null && partOffset == offset && partLength == length;
}
public boolean isResolved(int offset, int length) {
return resolvedUnit != null && partOffset == offset && partLength == length;
}
@Override
public void validate(IRegion dirtyRegion, IValidationContext helper, IReporter reporter) {
// ISourceValidator
if (file == null) {
return;
}
int start = dirtyRegion.getOffset();
int length = dirtyRegion.getLength();
getResolvedUnit(start, length, document);
}
@Override
public void validate(IValidationContext helper, IReporter reporter) throws ValidationException {
// IValidator -- hopefully, not used
}
private void adjustPosition(AnalysisError[] errors, int delta) {
for (AnalysisError error : errors) {
int offset = error.getOffset() + delta;
ReflectionUtils.setField(error, "offset", offset);
}
}
private int[] getLineStarts() {
int n = document.getNumberOfLines();
int[] starts = new int[n];
int pos = 0;
try {
for (int i = 0; i < n; i++) {
starts[i] = pos;
int len = document.getLineLength(i);
pos += len;
}
} catch (BadLocationException ex) {
return new int[1];
}
return starts;
}
/**
* Add a fake source containing only the embedded Dart code to the analysis context, parse it,
* then delete the fake source. Leaving the fake source in place causes various problems elsewhere
* as most of the analysis routines assume all sources are always valid, and represent an entire
* file.
*
* @param offset index of the beginning of the Dart script partition
* @param length length of the Dart script partition
* @return a parsed compilation unit
*/
private CompilationUnit parseSource(int offset, int length) {
IDocument document = this.document; // Cache in case of async disconnect().
File file = this.file;
AnalysisContext analysisContext = getInputAnalysisContext();
if (analysisContext == null) {
return null;
}
try {
String code = document.get(offset, length);
File tempFile = new File(file.getParentFile(), file.getName() + offset + "p.dart");
Source source = new FileBasedSource(tempFile);
analysisContext.setContents(source, code);
parsedUnit = analysisContext.parseCompilationUnit(source);
analysisContext.setContents(source, null);
ChangeSet changeSet = new ChangeSet();
changeSet.removedSource(source);
analysisContext.applyChanges(changeSet);
} catch (BadLocationException ex) {
Activator.logError(ex);
return null;
} catch (AnalysisException ex) {
Activator.logError(ex);
return null;
}
partOffset = offset;
partLength = length;
return parsedUnit;
}
private void reset() {
partOffset = partLength = -1;
parsedUnit = resolvedUnit = null;
}
/**
* Add a fake source containing only the embedded Dart code to the analysis context, parse and
* resolve it, record the error messages, then delete the fake source. Leaving the fake source in
* place causes various problems elsewhere as most of the analysis routines assume all sources are
* always valid, and represent an entire file.
*
* @param offset index of the beginning of the Dart script partition
* @param length length of the Dart script partition
* @return a resolved compilation unit
*/
private CompilationUnit resolveParsedUnit(int offset, int length) {
IDocument document = this.document; // Cache in case of async disconnect().
File file = this.file;
AnalysisContext analysisContext = getInputAnalysisContext();
if (analysisContext == null) {
return null;
}
try {
String code = document.get(offset, length);
File tempFile = new File(file.getParentFile(), file.getName() + offset + "r.dart");
Source source = new FileBasedSource(tempFile);
analysisContext.setContents(source, code);
LibraryElement library = analysisContext.computeLibraryElement(source);
resolvedUnit = analysisContext.resolveCompilationUnit(source, library);
parsedUnit = resolvedUnit;
AnalysisErrorInfo errorInfo = analysisContext.getErrors(source);
adjustPosition(errorInfo.getErrors(), offset);
AnalysisMarkerManager.getInstance().queueErrors(
resource,
new LineInfo(getLineStarts()),
errorInfo.getErrors());
analysisContext.setContents(source, null);
ChangeSet changeSet = new ChangeSet();
changeSet.removedSource(source);
analysisContext.applyChanges(changeSet);
} catch (AnalysisException ex) {
Activator.logError(ex);
return null;
} catch (BadLocationException ex) {
Activator.logError(ex);
return null;
}
partOffset = offset;
partLength = length;
return resolvedUnit;
}
}