/******************************************************************************* * 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; } }