Java tutorial
/******************************************************************************* * Copyright (c) 2019 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is 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: * Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.windup.ui.internal.rules.delegate; import java.io.StringReader; import java.io.StringWriter; import java.net.URL; import java.util.Iterator; import java.util.List; import java.util.Optional; import javax.annotation.PostConstruct; import org.eclipse.core.resources.IStorage; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.action.Action; import org.eclipse.jface.commands.ActionHandler; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.jface.resource.ColorRegistry; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentListener; import org.eclipse.jface.text.IDocumentPartitioner; import org.eclipse.jface.text.ITextOperationTarget; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.hyperlink.URLHyperlink; import org.eclipse.jface.text.source.AnnotationModel; import org.eclipse.jface.text.source.CompositeRuler; import org.eclipse.jface.text.source.IAnnotationAccess; import org.eclipse.jface.text.source.IOverviewRuler; import org.eclipse.jface.text.source.ISharedTextColors; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.OverviewRuler; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.mylyn.internal.wikitext.ui.WikiTextUiPlugin; import org.eclipse.mylyn.internal.wikitext.ui.editor.MarkupProjectionViewer; import org.eclipse.mylyn.internal.wikitext.ui.editor.syntax.FastMarkupPartitioner; import org.eclipse.mylyn.internal.wikitext.ui.editor.syntax.MarkupDocumentProvider; import org.eclipse.mylyn.wikitext.markdown.MarkdownLanguage; import org.eclipse.mylyn.wikitext.parser.Attributes; import org.eclipse.mylyn.wikitext.parser.MarkupParser; import org.eclipse.mylyn.wikitext.parser.builder.HtmlDocumentBuilder; import org.eclipse.mylyn.wikitext.parser.markup.AbstractMarkupLanguage; import org.eclipse.mylyn.wikitext.ui.editor.MarkupSourceViewerConfiguration; import org.eclipse.swt.SWT; import org.eclipse.swt.browser.Browser; import org.eclipse.swt.browser.LocationEvent; import org.eclipse.swt.browser.LocationListener; import org.eclipse.swt.browser.ProgressAdapter; import org.eclipse.swt.browser.ProgressEvent; import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IWorkbenchPreferenceConstants; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.browser.IWebBrowser; import org.eclipse.ui.editors.text.EditorsUI; import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.handlers.IHandlerActivation; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.internal.editors.text.EditorsPlugin; import org.eclipse.ui.texteditor.AnnotationPreference; import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; import org.eclipse.ui.texteditor.MarkerAnnotationPreferences; import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; import org.eclipse.wst.xml.core.internal.contentmodel.CMElementDeclaration; import org.eclipse.wst.xml.core.internal.contentmodel.CMNode; import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery; import org.jboss.tools.windup.ui.WindupUIPlugin; import org.jboss.tools.windup.ui.internal.Messages; import org.jboss.tools.windup.ui.internal.editor.AddNodeAction; import org.jboss.tools.windup.ui.internal.editor.ElementAttributesContainer; import org.jboss.tools.windup.ui.internal.editor.RulesetElementUiDelegateFactory.RulesetConstants; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import com.google.common.base.Objects; @SuppressWarnings("restriction") public class HintMessageTab extends ElementAttributesContainer { protected static final int VERTICAL_RULER_WIDTH = 12; private static final String CSS_CLASS_EDITOR_PREVIEW = "editorPreview"; //$NON-NLS-1$ private Browser browser; private IDocument document; private SourceViewer sourceViewer; private MarkdownLanguage language = new MarkdownLanguage(); private IHandlerService handlerService; private IHandlerActivation contentAssistHandlerActivation; private Section sourceSection; private Section previewSection; @PostConstruct public void createControls(Composite parent) { handlerService = PlatformUI.getWorkbench().getService(IHandlerService.class); CTabFolder folder = createMessagesSection(parent); createSourceTab(folder); CTabItem previewItem = createPreviewTab(folder); folder.setSelection(previewItem); updatePreview(); } private CTabFolder createMessagesSection(Composite parent) { parent = toolkit.createComposite(parent); GridLayoutFactory.fillDefaults().extendedMargins(0, 0, 0, 5).applyTo(parent); GridDataFactory.fillDefaults().grab(true, true).applyTo(parent); ColorRegistry reg = JFaceResources.getColorRegistry(); Color c1 = reg.get("org.eclipse.ui.workbench.ACTIVE_TAB_BG_START"), //$NON-NLS-1$ c2 = reg.get("org.eclipse.ui.workbench.ACTIVE_TAB_BG_END"); //$NON-NLS-1$ CTabFolder folder = new CTabFolder(parent, SWT.NO_REDRAW_RESIZE | SWT.FLAT); folder.setSelectionBackground(new Color[] { c1, c2 }, new int[] { 100 }, true); folder.setSelectionForeground(reg.get("org.eclipse.ui.workbench.ACTIVE_TAB_TEXT_COLOR")); //$NON-NLS-1$ folder.setSimple(PlatformUI.getPreferenceStore() .getBoolean(IWorkbenchPreferenceConstants.SHOW_TRADITIONAL_STYLE_TABS)); folder.setBorderVisible(true); GridDataFactory.fillDefaults().grab(true, true).applyTo(folder); GridData gd = new GridData(GridData.FILL_BOTH); gd.horizontalSpan = 2; folder.setLayoutData(gd); folder.setFont(parent.getFont()); return folder; } private CTabItem createSourceTab(CTabFolder folder) { CTabItem item = new CTabItem(folder, SWT.BORDER); item.setText(Messages.RulesetEditor_messageSection); item.setImage(WindupUIPlugin.getDefault().getImageRegistry().get(WindupUIPlugin.IMG_MARKDOWN)); Composite client = toolkit.createComposite(folder); GridLayoutFactory.fillDefaults().applyTo(client); GridDataFactory.fillDefaults().grab(true, true).applyTo(client); item.setControl(client); createSourceViewer(client); return item; } private CTabItem createPreviewTab(CTabFolder folder) { CTabItem item = new CTabItem(folder, SWT.BORDER); item.setText(Messages.RulesetEditor_previewSection); item.setImage(WindupUIPlugin.getDefault().getImageRegistry().get(WindupUIPlugin.IMG_REPORT)); Composite client = toolkit.createComposite(folder); GridLayoutFactory.fillDefaults().applyTo(client); GridDataFactory.fillDefaults().grab(true, true).applyTo(client); item.setControl(client); createBrowser(client); return item; } @Override protected void bind() { String message = getElementMessage(); if (!Objects.equal(document.get(), message)) { document.set(getElementMessage()); } sourceViewer.invalidateTextPresentation(); updatePreview(); } public Section getSourceEditorContainer() { return sourceSection; } public Section getBrowserContainer() { return previewSection; } private void createBrowser(Composite parent) { browser = new Browser(parent, SWT.NONE); GridDataFactory.fillDefaults().grab(true, true).hint(SWT.DEFAULT, 150).applyTo(browser); browser.addLocationListener(new LocationListener() { public void changed(LocationEvent event) { event.doit = false; } public void changing(LocationEvent event) { // if it looks like an absolute URL if (event.location.matches("([a-zA-Z]{3,8})://?.*")) { //$NON-NLS-1$ // workaround for browser problem (bug 262043) int idxOfSlashHash = event.location.indexOf("/#"); //$NON-NLS-1$ if (idxOfSlashHash != -1) { // allow javascript-based scrolling to work if (!event.location.startsWith("file:///#")) { //$NON-NLS-1$ event.doit = false; } return; } // workaround end event.doit = false; try { IWebBrowser webBrowser = PlatformUI.getWorkbench().getBrowserSupport() .createBrowser("org.eclipse.ui.browser"); //$NON-NLS-1$ webBrowser.openURL(new URL(event.location)); } catch (Exception e) { new URLHyperlink(new Region(0, 1), event.location).open(); } } } }); } @SuppressWarnings({ "unchecked", "rawtypes" }) private CMElementDeclaration getMessageCmNode() { List candidates = modelQuery.getAvailableContent(element, elementDeclaration, ModelQuery.VALIDITY_STRICT); Optional<CMElementDeclaration> found = candidates.stream().filter(candidate -> { if (candidate instanceof CMElementDeclaration) { return RulesetConstants.MESSAGE.equals(((CMElementDeclaration) candidate).getElementName()); } return false; }).findFirst(); if (found.isPresent()) { return found.get(); } return null; } private Node getMessageNode() { NodeList list = element.getElementsByTagName(RulesetConstants.MESSAGE); if (list.getLength() > 0) { return list.item(0); } return null; } private String getElementMessage() { String message = ""; Element node = (Element) getMessageNode(); if (node != null) { message = getCdataMessage(node); if (message == null) { message = contentHelper.getNodeValue(node); } } return message == null ? "" : message; } private String getCdataMessage(Node messageNode) { String message = null; Node cdataNode = getCdataNode(messageNode); if (cdataNode != null) { message = contentHelper.getNodeValue(cdataNode); } return message; } private Node getCdataNode(Node messageNode) { NodeList childList = messageNode.getChildNodes(); for (int i = 0; i < childList.getLength(); i++) { Node child = childList.item(i); if (Objects.equal(child.getNodeName(), RulesetConstants.CDATA)) { return child; } } return null; } protected void setMessage(String value) { Node node = getMessageNode(); CMNode cmNode = getMessageCmNode(); try { model.aboutToChangeModel(); if (node != null) { Node cdataNode = getCdataNode(node); String currentMessage = contentHelper.getNodeValue(node); if (currentMessage != null && !currentMessage.isEmpty()) { contentHelper.setNodeValue(node, value); } else if (cdataNode != null) { contentHelper.setNodeValue(cdataNode, value); } else { createCdataNode(node, value); } } else { AddNodeAction newNodeAction = new AddNodeAction(model, cmNode, element, element.getChildNodes().getLength()); newNodeAction.runWithoutTransaction(); List<Node> newNodes = newNodeAction.getResult(); if (!newNodes.isEmpty()) { node = newNodes.get(0); contentHelper.setNodeValue(node, ""); createCdataNode(node, value); } } } finally { model.changedModel(); } } private void createCdataNode(Node messageNode, String value) { Node cdataNode = messageNode.getOwnerDocument().createCDATASection(value); messageNode.appendChild(cdataNode); } protected void createSourceViewer(Composite parent) { String string = getElementMessage(); IStorage storage = new StringInput.StringStorage(string); IEditorInput editorInput = new StringInput(storage); CompositeRuler ruler = new CompositeRuler(); ISharedTextColors colors = EditorsPlugin.getDefault().getSharedTextColors(); int styles = SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI | SWT.BORDER | SWT.FULL_SELECTION | SWT.WRAP; IOverviewRuler overviewRuler = new OverviewRuler(new DefaultMarkerAnnotationAccess(), VERTICAL_RULER_WIDTH, colors); this.sourceViewer = new MarkupProjectionViewer(parent, ruler, overviewRuler, true, styles); ElementDetailsSection.addScrollListener(sourceViewer.getTextWidget()); GridDataFactory.fillDefaults().grab(true, true).hint(SWT.DEFAULT, 100).applyTo(sourceViewer.getControl()); try { MarkupDocumentProvider documentProvider = new MarkupDocumentProvider(); documentProvider.connect(editorInput); this.document = documentProvider.getDocument(editorInput); //sourceViewer.setDocument(document); ((AbstractMarkupLanguage) language).setEnableMacros(false); documentProvider.setMarkupLanguage(language); MarkupSourceViewerConfiguration sourceViewerConfiguration = new MarkupSourceViewerConfiguration( WikiTextUiPlugin.getDefault().getPreferenceStore()); sourceViewerConfiguration.initializeDefaultFonts(); sourceViewerConfiguration.setMarkupLanguage(language); sourceViewer.configure(sourceViewerConfiguration); IDocumentPartitioner partitioner = document.getDocumentPartitioner(); FastMarkupPartitioner fastMarkupPartitioner = (FastMarkupPartitioner) partitioner; fastMarkupPartitioner.setMarkupLanguage(language); } catch (CoreException e) { WindupUIPlugin.log(e); } sourceViewer.getTextWidget().addFocusListener(new FocusListener() { @Override public void focusLost(FocusEvent e) { deactivateEditorHandlers(); } @Override public void focusGained(FocusEvent e) { activatEditorHandlers(); } }); IDocumentListener documentListener = new IDocumentListener() { public void documentAboutToBeChanged(DocumentEvent event) { } public void documentChanged(DocumentEvent event) { if (!blockNotification) { setMessage(document.get()); updatePreview(); } } }; sourceViewer.getTextWidget().addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { document.removeDocumentListener(documentListener); deactivateEditorHandlers(); } }); document.addDocumentListener(documentListener); configureAsEditor(sourceViewer, (Document) document); } private void configureAsEditor(ISourceViewer viewer, Document document) { IAnnotationAccess annotationAccess = new DefaultMarkerAnnotationAccess(); final SourceViewerDecorationSupport support = new SourceViewerDecorationSupport(viewer, null, annotationAccess, EditorsUI.getSharedTextColors()); Iterator<?> e = new MarkerAnnotationPreferences().getAnnotationPreferences().iterator(); while (e.hasNext()) { support.setAnnotationPreference((AnnotationPreference) e.next()); } support.install(EditorsUI.getPreferenceStore()); viewer.getTextWidget().addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { support.uninstall(); } }); AnnotationModel annotationModel = new AnnotationModel(); viewer.setDocument(document, annotationModel); } private void activatEditorHandlers() { contentAssistHandlerActivation = handlerService.activateHandler( ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS, new ActionHandler(new AssistAction(sourceViewer))); } private void deactivateEditorHandlers() { if (contentAssistHandlerActivation != null) { handlerService.deactivateHandler(contentAssistHandlerActivation); contentAssistHandlerActivation = null; } } private static class AssistAction extends Action { private ITextOperationTarget fOperationTarget; public AssistAction(SourceViewer sourceViewer) { this.fOperationTarget = sourceViewer.getTextOperationTarget(); } @Override public void run() { BusyIndicator.showWhile(Display.getDefault(), new Runnable() { @Override public void run() { fOperationTarget.doOperation(ISourceViewer.CONTENTASSIST_PROPOSALS); } }); } } private void updatePreview() { final int verticalScrollbarPos = 0; String title = file == null ? "" : file.getName(); //$NON-NLS-1$ if (title.lastIndexOf('.') != -1) { title = title.substring(0, title.lastIndexOf('.')); } StringWriter writer = new StringWriter(); HtmlDocumentBuilder builder = new HtmlDocumentBuilder(writer) { @Override protected void emitAnchorHref(String href) { if (href != null && href.startsWith("#")) { //$NON-NLS-1$ writer.writeAttribute("onclick", //$NON-NLS-1$ String.format("javascript: window.location.hash = '%s'; return false;", href)); //$NON-NLS-1$ writer.writeAttribute("href", "#"); //$NON-NLS-1$//$NON-NLS-2$ } else { super.emitAnchorHref(href); } } @Override public void beginHeading(int level, Attributes attributes) { attributes.appendCssClass(CSS_CLASS_EDITOR_PREVIEW); super.beginHeading(level, attributes); } @Override public void beginBlock(BlockType type, Attributes attributes) { attributes.appendCssClass(CSS_CLASS_EDITOR_PREVIEW); super.beginBlock(type, attributes); } }; builder.setTitle(title); String css = WikiTextUiPlugin.getDefault().getPreferences().getMarkupViewerCss(); builder.addCssStylesheet(new HtmlDocumentBuilder.Stylesheet(new StringReader(css))); AbstractMarkupLanguage markupLanguage = (AbstractMarkupLanguage) language.clone(); markupLanguage.setEnableMacros(true); markupLanguage.setFilterGenerativeContents(false); markupLanguage.setBlocksOnly(false); MarkupParser markupParser = new MarkupParser(); markupParser.setBuilder(builder); markupParser.setMarkupLanguage(markupLanguage); markupParser.parse(document.get()); browser.addProgressListener(new ProgressAdapter() { @Override public void completed(ProgressEvent event) { browser.removeProgressListener(this); browser.execute(String.format("window.scrollTo(0,%d);", verticalScrollbarPos)); //$NON-NLS-1$ } }); String xhtml = writer.toString(); browser.setText(xhtml); } }