1 /**
2  *  Copyright (c) 2017 Angelo ZERR.
3  *
4  *  This program and the accompanying materials
5  *  are made available under the terms of the Eclipse Public License 2.0
6  *  which accompanies this distribution, and is available at
7  *  https://www.eclipse.org/legal/epl-2.0/
8  *
9  *  SPDX-License-Identifier: EPL-2.0
10  *
11  *  Contributors:
12  *  Angelo Zerr <angelo.zerr@gmail.com> - [CodeMining] Provide extension point for CodeMining - Bug 528419
13  */
14 package org.eclipse.ui.internal.texteditor.codemining;
15 
16 import org.eclipse.core.expressions.ElementHandler;
17 import org.eclipse.core.expressions.EvaluationContext;
18 import org.eclipse.core.expressions.EvaluationResult;
19 import org.eclipse.core.expressions.Expression;
20 import org.eclipse.core.expressions.ExpressionConverter;
21 
22 import org.eclipse.core.runtime.Assert;
23 import org.eclipse.core.runtime.CoreException;
24 import org.eclipse.core.runtime.IConfigurationElement;
25 import org.eclipse.core.runtime.IStatus;
26 import org.eclipse.core.runtime.Status;
27 
28 import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider;
29 import org.eclipse.jface.text.codemining.ICodeMiningProvider;
30 import org.eclipse.jface.text.source.ISourceViewer;
31 
32 import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
33 
34 import org.eclipse.ui.texteditor.ITextEditor;
35 
36 /**
37  * Describes an extension to the <code>codeMiningProviders</code> extension
38  * point.
39  * <p>
40  * This class is not intended to be subclassed by clients.
41  * </p>
42  *
43  * @since 3.10
44  * @noextend This class is not intended to be subclassed by clients.
45  */
46 class CodeMiningProviderDescriptor {
47 
48 	/** Name of the <code>label</code> attribute. */
49 	private static final String LABEL_ATTRIBUTE = "label"; //$NON-NLS-1$
50 	/** Name of the <code>class</code> attribute. */
51 	private static final String CLASS_ATTRIBUTE = "class"; //$NON-NLS-1$
52 	/** Name of the <code>id</code> attribute. */
53 	private static final String ID_ATTRIBUTE = "id"; //$NON-NLS-1$
54 	/** Name of the <code>enabledWhen</code> attribute. **/
55 	private static final String ENABLED_WHEN_ATTR = "enabledWhen"; //$NON-NLS-1$
56 
57 	/** The configuration element describing this extension. */
58 	private IConfigurationElement fConfiguration;
59 	/** The value of the <code>label</code> attribute, if read. */
60 	private String fLabel;
61 	/** The value of the <code>id</code> attribute, if read. */
62 	private String fId;
63 	/** The expression value of the <code>enabledWhen</code> attribute. */
64 	private final Expression fEnabledWhen;
65 
66 	/**
67 	 * Creates a new descriptor for <code>element</code>.
68 	 * <p>
69 	 * This method is for internal use only.
70 	 * </p>
71 	 *
72 	 * @param element
73 	 *            the extension point element to be described.
74 	 * @throws CoreException
75 	 *             when <code>enabledWhen</code> expression is not valid.
76 	 */
CodeMiningProviderDescriptor(IConfigurationElement element)77 	public CodeMiningProviderDescriptor(IConfigurationElement element) throws CoreException {
78 		Assert.isLegal(element != null);
79 		fConfiguration = element;
80 		fEnabledWhen = createEnabledWhen(fConfiguration, getId());
81 	}
82 
83 	/**
84 	 * Returns the expression {@link Expression} declared in the
85 	 * <code>enabledWhen</code> element.
86 	 *
87 	 * @param configElement
88 	 *            the configuration element
89 	 * @param id
90 	 *            the id of the codemining provider.
91 	 * @return the expression {@link Expression} declared in the enabledWhen
92 	 *         element.
93 	 * @throws CoreException
94 	 *             when enabledWhen expression is not valid.
95 	 */
createEnabledWhen(IConfigurationElement configElement, String id)96 	private static Expression createEnabledWhen(IConfigurationElement configElement, String id) throws CoreException {
97 		final IConfigurationElement[] children = configElement.getChildren(ENABLED_WHEN_ATTR);
98 		if (children.length > 0) {
99 			IConfigurationElement[] subChildren = children[0].getChildren();
100 			if (subChildren.length != 1) {
101 				throw new CoreException(new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID,
102 						"One <enabledWhen> element is accepted. Disabling " + id)); //$NON-NLS-1$
103 			}
104 			final ElementHandler elementHandler = ElementHandler.getDefault();
105 			final ExpressionConverter converter = ExpressionConverter.getDefault();
106 			return elementHandler.create(converter, subChildren[0]);
107 		}
108 		return null;
109 	}
110 
111 	/**
112 	 * Reads (if needed) and returns the label of this extension.
113 	 *
114 	 * @return the label for this extension.
115 	 */
getLabel()116 	public String getLabel() {
117 		if (fLabel == null) {
118 			fLabel = fConfiguration.getAttribute(LABEL_ATTRIBUTE);
119 			Assert.isNotNull(fLabel);
120 		}
121 		return fLabel;
122 	}
123 
124 	/**
125 	 * Reads (if needed) and returns the id of this extension.
126 	 *
127 	 * @return the id for this extension.
128 	 */
getId()129 	public String getId() {
130 		if (fId == null) {
131 			fId = fConfiguration.getAttribute(ID_ATTRIBUTE);
132 			Assert.isNotNull(fId);
133 		}
134 		return fId;
135 	}
136 
137 	/**
138 	 * Creates a codemining provider as described in the extension's xml. and null
139 	 * otherwise.
140 	 *
141 	 * @param editor
142 	 *            the text editor
143 	 *
144 	 * @return the created codemining provider and null otherwise.
145 	 */
createCodeMiningProvider(ITextEditor editor)146 	protected ICodeMiningProvider createCodeMiningProvider(ITextEditor editor) {
147 		try {
148 			Object extension = fConfiguration.createExecutableExtension(CLASS_ATTRIBUTE);
149 			if (extension instanceof ICodeMiningProvider) {
150 				if (extension instanceof AbstractCodeMiningProvider) {
151 					((AbstractCodeMiningProvider) extension).setContext(editor);
152 				}
153 				return (ICodeMiningProvider) extension;
154 			} else {
155 				String message = "Invalid extension to codeMiningProviders. Must extends ICodeMiningProvider: " //$NON-NLS-1$
156 						+ getId();
157 				TextEditorPlugin.getDefault().getLog()
158 						.log(new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, message));
159 				return null;
160 			}
161 		} catch (CoreException e) {
162 			TextEditorPlugin.getDefault().getLog().log(new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID,
163 					"Error while creating codeMiningProvider: " + getId(), e)); //$NON-NLS-1$
164 			return null;
165 		}
166 	}
167 
168 	/**
169 	 * Returns true if the given viewer, editor matches the enabledWhen expression
170 	 * and false otherwise.
171 	 *
172 	 * @param viewer
173 	 *            the viewer
174 	 * @param editor
175 	 *            the editor
176 	 * @return true if the given viewer, editor matches the enabledWhen expression
177 	 *         and false otherwise.
178 	 */
matches(ISourceViewer viewer, ITextEditor editor)179 	public boolean matches(ISourceViewer viewer, ITextEditor editor) {
180 		if (fEnabledWhen == null) {
181 			return true;
182 		}
183 		EvaluationContext context = new EvaluationContext(null, editor);
184 		context.setAllowPluginActivation(true);
185 		context.addVariable("viewer", viewer); //$NON-NLS-1$
186 		context.addVariable("editor", editor); //$NON-NLS-1$
187 		context.addVariable("editorInput", editor.getEditorInput()); //$NON-NLS-1$
188 		try {
189 			return fEnabledWhen.evaluate(context) == EvaluationResult.TRUE;
190 		} catch (CoreException e) {
191 			TextEditorPlugin.getDefault().getLog().log(
192 					new Status(IStatus.ERROR, TextEditorPlugin.PLUGIN_ID, "Error while 'enabledWhen' evaluation", e)); //$NON-NLS-1$
193 			return false;
194 		}
195 	}
196 
197 }
198