/******************************************************************************* | |
* Copyright (c) 2006, 2012 IBM Corporation and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/epl-v10.html | |
* | |
* Contributors: | |
* IBM Corporation - initial API and implementation | |
* | |
*******************************************************************************/ | |
package org.eclipse.wst.jsdt.web.core.internal.validation; | |
import java.io.IOException; | |
import java.util.ArrayList; | |
import java.util.HashSet; | |
import java.util.List; | |
import java.util.Locale; | |
import java.util.Set; | |
import org.eclipse.core.resources.IFile; | |
import org.eclipse.core.resources.IMarker; | |
import org.eclipse.core.resources.IProject; | |
import org.eclipse.core.resources.IResource; | |
import org.eclipse.core.resources.IResourceProxy; | |
import org.eclipse.core.resources.IResourceProxyVisitor; | |
import org.eclipse.core.resources.IWorkspaceRoot; | |
import org.eclipse.core.resources.ResourcesPlugin; | |
import org.eclipse.core.runtime.CoreException; | |
import org.eclipse.core.runtime.IConfigurationElement; | |
import org.eclipse.core.runtime.IExecutableExtension; | |
import org.eclipse.core.runtime.IProgressMonitor; | |
import org.eclipse.core.runtime.Path; | |
import org.eclipse.core.runtime.Platform; | |
import org.eclipse.jface.text.BadLocationException; | |
import org.eclipse.jface.text.IDocument; | |
import org.eclipse.wst.jsdt.core.compiler.IProblem; | |
import org.eclipse.wst.jsdt.web.core.internal.Logger; | |
import org.eclipse.wst.jsdt.web.core.javascript.IJsTranslation; | |
import org.eclipse.wst.jsdt.web.core.javascript.JsTranslationAdapter; | |
import org.eclipse.wst.jsdt.web.core.javascript.JsTranslationAdapterFactory; | |
import org.eclipse.wst.sse.core.StructuredModelManager; | |
import org.eclipse.wst.sse.core.internal.provisional.IStructuredModel; | |
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; | |
import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion; | |
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion; | |
import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionCollection; | |
import org.eclipse.wst.validation.AbstractValidator; | |
import org.eclipse.wst.validation.ValidationResult; | |
import org.eclipse.wst.validation.ValidationState; | |
import org.eclipse.wst.validation.internal.core.Message; | |
import org.eclipse.wst.validation.internal.core.ValidationException; | |
import org.eclipse.wst.validation.internal.operations.IWorkbenchContext; | |
import org.eclipse.wst.validation.internal.provisional.core.IMessage; | |
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 org.eclipse.wst.xml.core.internal.provisional.document.IDOMDocument; | |
import org.eclipse.wst.xml.core.internal.provisional.document.IDOMModel; | |
public class JsValidator extends AbstractValidator implements IValidator, IExecutableExtension { | |
private static final boolean DEBUG = Boolean.valueOf(Platform.getDebugOption("org.eclipse.wst.jsdt.web.core/debug/jsvalidator")).booleanValue(); //$NON-NLS-1$ | |
private IValidator fMessageOriginator; | |
private Set fValidFileExts = new HashSet(); | |
private static final String[] METADATA_FILES = new String[]{".settings/.jsdtscope",".settings/org.eclipse.wst.jsdt.ui.superType.container",".settings/org.eclipse.wst.jsdt.ui.superType.name"}; | |
private static final String JAVASCRIPT_TASK_MARKER_TYPE = "org.eclipse.wst.jsdt.core.task"; //$NON-NLS-1$ | |
// private static String [] jsdtValidator = {"org.eclipse.wst.jsdt.web.core.internal.validation.JsBatchValidator"}; //$NON-NLS-1$ | |
protected class LocalizedMessage extends Message { | |
private String _message = null; | |
public LocalizedMessage(int severity, String messageText) { | |
this(severity, messageText, null); | |
} | |
public LocalizedMessage(int severity, String messageText, IResource targetObject) { | |
this(severity, messageText, (Object) targetObject); | |
} | |
public LocalizedMessage(int severity, String messageText, Object targetObject) { | |
super(null, severity, null); | |
setLocalizedMessage(messageText); | |
setTargetObject(targetObject); | |
} | |
public String getLocalizedMessage() { | |
return _message; | |
} | |
public String getText() { | |
return getLocalizedMessage(); | |
} | |
public String getText(ClassLoader cl) { | |
return getLocalizedMessage(); | |
} | |
public String getText(Locale l) { | |
return getLocalizedMessage(); | |
} | |
public String getText(Locale l, ClassLoader cl) { | |
return getLocalizedMessage(); | |
} | |
public void setLocalizedMessage(String message) { | |
_message = message; | |
} | |
} | |
public JsValidator() { | |
this.fMessageOriginator = this; | |
} | |
/** | |
* Creates an IMessage from an IProblem | |
* | |
* @param problem | |
* @param f | |
* @param translation | |
* @param textDoc | |
* @return message representation of the problem, or null if it could not | |
* create one | |
*/ | |
private IMessage createMessageFromProblem(IProblem problem, IFile f, IJsTranslation translation, IDocument textDoc) { | |
int sourceStart = problem.getSourceStart(); | |
int sourceEnd = problem.getSourceEnd(); | |
if (sourceStart == -1) { | |
return null; | |
} | |
sourceStart = translation.getWebPageOffset(sourceStart); | |
sourceEnd = translation.getWebPageOffset(sourceEnd); | |
/* | |
* Bug 241794 - Validation shows errors when using JSP Expressions | |
* inside JavaScript code | |
*/ | |
IStructuredDocument doc = (IStructuredDocument) textDoc; | |
IStructuredDocumentRegion documentRegion = doc.getRegionAtCharacterOffset(sourceStart); | |
if (documentRegion != null) { | |
ITextRegion textRegion = documentRegion.getRegionAtCharacterOffset(sourceStart); | |
/* | |
* Filter out problems from areas that aren't simple JavaScript, | |
* e.g. JSP. | |
*/ | |
if (textRegion != null && textRegion instanceof ITextRegionCollection) | |
return null; | |
} | |
int sev = problem.isError() ? IMessage.HIGH_SEVERITY : (problem.isWarning() ? IMessage.NORMAL_SEVERITY : IMessage.LOW_SEVERITY); | |
IMessage m = new LocalizedMessage(sev, problem.getMessage(), f); | |
// line numbers for marker starts @ 1 | |
// line numbers from document starts @ 0 | |
try { | |
int lineNo = textDoc.getLineOfOffset(sourceStart) + 1; | |
m.setLineNo(lineNo); | |
m.setOffset(sourceStart); | |
m.setLength(sourceEnd - sourceStart + 1); | |
} | |
catch (BadLocationException e) { | |
Logger.logException(e); | |
} | |
return m; | |
} | |
void performValidation(IFile f, IReporter reporter, IStructuredModel model, boolean inBatch) { | |
if (model instanceof IDOMModel) { | |
IDOMModel domModel = (IDOMModel) model; | |
JsTranslationAdapterFactory.setupAdapterFactory(domModel); | |
IDOMDocument xmlDoc = domModel.getDocument(); | |
JsTranslationAdapter translationAdapter = (JsTranslationAdapter) xmlDoc.getAdapterFor(IJsTranslation.class); | |
//translationAdapter.resourceChanged(); | |
IJsTranslation translation = translationAdapter.getJsTranslation(false); | |
if (!reporter.isCancelled()) { | |
translation.setProblemCollectingActive(true); | |
translation.reconcileCompilationUnit(); | |
List problems = translation.getProblems(); | |
// only update task markers if the model is the same as what's on disk | |
boolean updateTasks = !domModel.isDirty() && f != null && f.isAccessible(); | |
if (updateTasks) { | |
// remove old JavaScript task markers | |
try { | |
IMarker[] foundMarkers = f.findMarkers(JAVASCRIPT_TASK_MARKER_TYPE, true, IResource.DEPTH_ONE); | |
for (int i = 0; i < foundMarkers.length; i++) { | |
foundMarkers[i].delete(); | |
} | |
} | |
catch (CoreException e) { | |
Logger.logException(e); | |
} | |
} | |
// if(!inBatch) reporter.removeAllMessages(this, f); | |
// add new messages | |
for (int i = 0; i < problems.size() && !reporter.isCancelled(); i++) { | |
IProblem problem = (IProblem) problems.get(i); | |
IMessage m = createMessageFromProblem(problem, f, translation, domModel.getStructuredDocument()); | |
if (m != null) { | |
if (problem.getID() == IProblem.Task) { | |
if (updateTasks) { | |
// add new JavaScript task marker | |
try { | |
IMarker task = f.createMarker(JAVASCRIPT_TASK_MARKER_TYPE); | |
task.setAttribute(IMarker.LINE_NUMBER, new Integer(m.getLineNumber())); | |
task.setAttribute(IMarker.CHAR_START, new Integer(m.getOffset())); | |
task.setAttribute(IMarker.CHAR_END, new Integer(m.getOffset() + m.getLength())); | |
task.setAttribute(IMarker.MESSAGE, m.getText()); | |
task.setAttribute(IMarker.USER_EDITABLE, Boolean.FALSE); | |
switch (m.getSeverity()) { | |
case IMessage.HIGH_SEVERITY: { | |
task.setAttribute(IMarker.PRIORITY, new Integer(IMarker.PRIORITY_HIGH)); | |
task.setAttribute(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_ERROR)); | |
} | |
break; | |
case IMessage.LOW_SEVERITY : { | |
task.setAttribute(IMarker.PRIORITY, new Integer(IMarker.PRIORITY_LOW)); | |
task.setAttribute(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_INFO)); | |
} | |
break; | |
default : { | |
task.setAttribute(IMarker.PRIORITY, new Integer(IMarker.PRIORITY_NORMAL)); | |
task.setAttribute(IMarker.SEVERITY, new Integer(IMarker.SEVERITY_WARNING)); | |
} | |
} | |
} | |
catch (CoreException e) { | |
Logger.logException(e); | |
} | |
} | |
} | |
else { | |
reporter.addMessage(fMessageOriginator, m); | |
} | |
} | |
} | |
} | |
} | |
} | |
/* Read the definition for this validator and the declared valid file extensions | |
* @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, java.lang.String, java.lang.Object) | |
*/ | |
public void setInitializationData(IConfigurationElement config, String propertyName, Object data) throws CoreException { | |
IConfigurationElement[] includes = config.getChildren("include"); //$NON-NLS-1$ | |
for (int i = 0; i < includes.length; i++) { | |
IConfigurationElement[] fileexts = includes[i].getChildren("fileext"); //$NON-NLS-1$ | |
for (int j = 0; j < fileexts.length; j++) { | |
String fileext = fileexts[j].getAttribute("ext"); //$NON-NLS-1$ | |
if (fileext != null) { | |
fValidFileExts.add(fileext); | |
} | |
} | |
} | |
} | |
boolean shouldValidate(IFile file) { | |
IResource resource = file; | |
do { | |
if (resource.isDerived() || resource.isTeamPrivateMember() || !resource.isAccessible() || resource.getName().charAt(0) == '.') { | |
return false; | |
} | |
resource = resource.getParent(); | |
} while ((resource.getType() & IResource.PROJECT) == 0); | |
return fValidFileExts.isEmpty() || fValidFileExts.contains(file.getFileExtension()); | |
} | |
public void validate(IValidationContext helper, IReporter reporter) throws ValidationException { | |
/* Added by BC ---- */ | |
// if(true) return; | |
/* end Added by BC ---- */ | |
String[] uris = helper.getURIs(); | |
if (uris.length > 0) { | |
IWorkspaceRoot wsRoot = ResourcesPlugin.getWorkspace().getRoot(); | |
IFile currentFile = null; | |
for (int i = 0; i < uris.length && !reporter.isCancelled(); i++) { | |
currentFile = wsRoot.getFile(new Path(uris[i])); | |
reporter.removeAllMessages(this, currentFile); | |
if (currentFile != null && currentFile.exists()) { | |
if (shouldValidate(currentFile) ){ //&& fragmentCheck(currentFile)) { | |
int percent = (i * 100) / uris.length + 1; | |
IMessage message = new LocalizedMessage(IMessage.LOW_SEVERITY, percent + "% " + uris[i]); //$NON-NLS-1$ | |
reporter.displaySubtask(this, message); | |
validateFile(currentFile, reporter); | |
} | |
if (DEBUG) { | |
System.out.println("validating: [" + uris[i] + "]"); //$NON-NLS-1$ //$NON-NLS-2$ | |
} | |
} | |
} | |
} else { | |
// if uris[] length 0 -> validate() gets called for each project | |
if (helper instanceof IWorkbenchContext) { | |
IProject project = ((IWorkbenchContext) helper).getProject(); | |
JSFileVisitor visitor = new JSFileVisitor(reporter); | |
try { | |
// collect all jsp files for the project | |
project.accept(visitor, IResource.DEPTH_INFINITE); | |
} catch (CoreException e) { | |
if (DEBUG) { | |
e.printStackTrace(); | |
} | |
} | |
IFile[] files = visitor.getFiles(); | |
for (int i = 0; i < files.length && !reporter.isCancelled(); i++) { | |
int percent = (i * 100) / files.length + 1; | |
IMessage message = new LocalizedMessage(IMessage.LOW_SEVERITY, percent + "% " + files[i].getFullPath().toString()); //$NON-NLS-1$ | |
reporter.displaySubtask(this, message); | |
validateFile(files[i], reporter); | |
if (DEBUG) { | |
System.out.println("validating: [" + files[i] + "]"); //$NON-NLS-1$ //$NON-NLS-2$ | |
} | |
} | |
} | |
} | |
} | |
protected class JSFileVisitor implements IResourceProxyVisitor { | |
private List fFiles = new ArrayList(); | |
private IReporter fReporter = null; | |
public JSFileVisitor(IReporter reporter) { | |
fReporter = reporter; | |
} | |
public final IFile[] getFiles() { | |
return (IFile[]) fFiles.toArray(new IFile[fFiles.size()]); | |
} | |
public boolean visit(IResourceProxy proxy) throws CoreException { | |
// check validation | |
if (fReporter.isCancelled()) { | |
return false; | |
} | |
if (proxy.getType() == IResource.FILE) { | |
if (Util.isJsType(proxy.getName())) { | |
IFile file = (IFile) proxy.requestResource(); | |
if (file.exists() && shouldValidate(file)) { | |
if (DEBUG) { | |
System.out.println("(+) JSPValidator adding file: " + file.getName()); //$NON-NLS-1$ | |
} | |
fFiles.add(file); | |
// don't search deeper for files | |
return false; | |
} | |
} | |
} | |
return true; | |
} | |
} | |
public void cleanup(IReporter reporter) { | |
// nothing to do | |
} | |
/** | |
* Validate one file. It's assumed that the file has JSP content type. | |
* | |
* @param f | |
* @param reporter | |
*/ | |
protected void validateFile(IFile f, IReporter reporter) { | |
if (JsValidator.DEBUG) { | |
Logger.log(Logger.INFO, getClass().getName() + " validating: " + f); //$NON-NLS-1$ | |
} | |
IStructuredModel model = null; | |
try { | |
// get jsp model, get tranlsation | |
model = StructuredModelManager.getModelManager().getModelForRead(f); | |
if (!reporter.isCancelled() && model != null) { | |
// get DOM model then translation | |
//WorkbenchReporter.removeAllMessages(f.getProject(), jsdtValidator, f.toString()); | |
//reporter.removeAllMessages(fMessageOriginator, f); | |
performValidation(f, reporter, model, false); | |
} | |
} catch (IOException e) { | |
Logger.logException(e); | |
} catch (CoreException e) { | |
Logger.logException(e); | |
} finally { | |
if (model != null) { | |
model.releaseFromRead(); | |
} | |
} | |
} | |
public ValidationResult validate(IResource resource, int kind, ValidationState state, IProgressMonitor monitor) { | |
if (resource.getType() != IResource.FILE || !shouldValidate((IFile) resource)) | |
return null; | |
ValidationResult result = new ValidationResult(); | |
IReporter reporter = result.getReporter(monitor); | |
IFile file = (IFile) resource; | |
validateFile(file, reporter); | |
result.setDependsOn(createDependencies(file)); | |
return result; | |
} | |
private IResource[] createDependencies(IFile file) { | |
IFile[] depends = new IFile[METADATA_FILES.length]; | |
for (int i = 0; i < METADATA_FILES.length; i++) { | |
depends[i] = file.getProject().getFile(METADATA_FILES[i]); | |
} | |
return depends; | |
} | |
} |