/* * Copyright 2007 Sascha Weinreuter * * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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 org.intellij.plugins.relaxNG.validation; import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.WindowManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlFile; import com.intellij.xml.XmlElementDescriptor; import com.thaiopensource.util.PropertyMapBuilder; import com.thaiopensource.util.UriOrFile; import com.thaiopensource.validate.SchemaReader; import com.thaiopensource.validate.ValidateProperty; import com.thaiopensource.validate.ValidationDriver; import com.thaiopensource.validate.auto.AutoSchemaReader; import com.thaiopensource.validate.rng.CompactSchemaReader; import com.thaiopensource.validate.rng.RngProperty; import org.intellij.plugins.relaxNG.compact.RncFileType; import org.intellij.plugins.relaxNG.model.descriptors.RngElementDescriptor; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import javax.swing.*; import java.io.IOException; import java.net.MalformedURLException; import java.util.concurrent.Future; @SuppressWarnings({ "ComponentNotRegistered" }) public class ValidateAction extends AnAction { private static final String CONTENT_NAME = "Validate RELAX NG"; private static final Key<NewErrorTreeViewPanel> KEY = Key.create("VALIDATING"); private static final Key<Boolean> IN_PROGRESS_KEY = Key.create("VALIDATION IN PROGRESS"); private final AnAction myOrigAction; public ValidateAction(AnAction origAction) { myOrigAction = origAction; copyFrom(origAction); setEnabledInModalContext(origAction.isEnabledInModalContext()); } @Override public void actionPerformed(AnActionEvent e) { if (!actionPerformedImpl(e)) { myOrigAction.actionPerformed(e); } } @Override public final void update(AnActionEvent e) { super.update(e); myOrigAction.update(e); final VirtualFile file = e.getData(CommonDataKeys.VIRTUAL_FILE); if (file != null) { if (file.getUserData(IN_PROGRESS_KEY) == Boolean.TRUE) { e.getPresentation().setEnabled(false); } } } private boolean actionPerformedImpl(AnActionEvent e) { final PsiFile file = e.getData(CommonDataKeys.PSI_FILE); if (file == null) { return false; } final Project project = e.getData(CommonDataKeys.PROJECT); if (project == null) { return false; } final RngElementDescriptor descriptor = getRootDescriptor(file); if (descriptor == null) return false; final PsiElement element = descriptor.getDeclaration(); final XmlFile xmlFile = PsiTreeUtil.getParentOfType(element, XmlFile.class); if (xmlFile == null) return false; final VirtualFile instanceFile = file.getVirtualFile(); final VirtualFile schemaFile = xmlFile.getVirtualFile(); if (instanceFile == null || schemaFile == null) { return true; } doRun(project, instanceFile, schemaFile); return true; } private static void doRun(final Project project, final VirtualFile instanceFile, final VirtualFile schemaFile) { saveFiles(instanceFile, schemaFile); final MessageViewHelper helper = new MessageViewHelper(project, CONTENT_NAME, KEY); helper.openMessageView(() -> doRun(project, instanceFile, schemaFile)); final Future<?> future = ApplicationManager.getApplication().executeOnPooledThread( () -> ApplicationManager.getApplication().runReadAction(() -> { final MessageViewHelper.ErrorHandler eh = helper.new ErrorHandler(); instanceFile.putUserData(IN_PROGRESS_KEY, Boolean.TRUE); try { doValidation(instanceFile, schemaFile, eh); } finally { instanceFile.putUserData(IN_PROGRESS_KEY, null); } SwingUtilities.invokeLater( () -> { if (!eh.hadErrorOrWarning()) { SwingUtilities.invokeLater( () -> { helper.close(); WindowManager.getInstance().getStatusBar(project).setInfo("No errors detected"); } ); } } ); })); helper.setProcessController(new NewErrorTreeViewPanel.ProcessController() { @Override public void stopProcess() { future.cancel(true); } @Override public boolean isProcessStopped() { return future.isDone(); } }); } @SuppressWarnings({ "ThrowableInstanceNeverThrown" }) private static void doValidation(VirtualFile instanceFile, VirtualFile schemaFile, org.xml.sax.ErrorHandler eh) { final SchemaReader sr = schemaFile.getFileType() == RncFileType.getInstance() ? CompactSchemaReader.getInstance() : new AutoSchemaReader(); final PropertyMapBuilder properties = new PropertyMapBuilder(); ValidateProperty.ERROR_HANDLER.put(properties, eh); // TODO: should some options dialog displayed before validating? RngProperty.CHECK_ID_IDREF.add(properties); try { final String schemaPath = VfsUtilCore.fixIDEAUrl(schemaFile.getUrl()); try { final ValidationDriver driver = new ValidationDriver(properties.toPropertyMap(), sr); final InputSource in = ValidationDriver.uriOrFileInputSource(schemaPath); in.setEncoding(schemaFile.getCharset().name()); if (driver.loadSchema(in)) { final String path = VfsUtilCore.fixIDEAUrl(instanceFile.getUrl()); try { driver.validate(ValidationDriver.uriOrFileInputSource(path)); } catch (IOException e1) { eh.fatalError(new SAXParseException(e1.getMessage(), null, UriOrFile.fileToUri(path), -1, -1, e1)); } } } catch (SAXParseException e1) { eh.fatalError(e1); } catch (IOException e1) { eh.fatalError(new SAXParseException(e1.getMessage(), null, UriOrFile.fileToUri(schemaPath), -1, -1, e1)); } } catch (SAXException | MalformedURLException e1) { // huh? Logger.getInstance(ValidateAction.class.getName()).error(e1); } } private static RngElementDescriptor getRootDescriptor(PsiFile file) { try { if (file instanceof XmlFile) { final XmlElementDescriptor descriptor = ((XmlFile)file).getDocument().getRootTag().getDescriptor(); if (descriptor instanceof RngElementDescriptor) { return (RngElementDescriptor)descriptor; } } } catch (NullPointerException e1) { // OK } return null; } @Override public boolean displayTextInToolbar() { return myOrigAction.displayTextInToolbar(); } @Override public void setDefaultIcon(boolean b) { myOrigAction.setDefaultIcon(b); } @Override public boolean isDefaultIcon() { return myOrigAction.isDefaultIcon(); } @Override public void setInjectedContext(boolean worksInInjected) { myOrigAction.setInjectedContext(worksInInjected); } @Override public boolean isInInjectedContext() { return myOrigAction.isInInjectedContext(); } public static void saveFiles(VirtualFile... files) { // ensure the validation/conversion runs on the current content final FileDocumentManager mgr = FileDocumentManager.getInstance(); for (VirtualFile f : files) { final Document document = mgr.getDocument(f); if (document != null) { mgr.saveDocument(document); } } } }