| /******************************************************************************* |
| * Copyright (c) 2007, 2016 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.help.internal; |
| |
| import java.lang.reflect.Array; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import javax.xml.parsers.DocumentBuilder; |
| import javax.xml.parsers.DocumentBuilderFactory; |
| import javax.xml.parsers.ParserConfigurationException; |
| |
| import org.eclipse.core.expressions.EvaluationResult; |
| import org.eclipse.core.expressions.Expression; |
| import org.eclipse.core.expressions.ExpressionConverter; |
| import org.eclipse.core.expressions.ExpressionTagNames; |
| import org.eclipse.core.expressions.IEvaluationContext; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.help.IUAElement; |
| import org.eclipse.help.internal.dynamic.FilterResolver; |
| import org.eclipse.help.internal.entityresolver.LocalEntityResolver; |
| import org.eclipse.help.internal.util.ProductPreferences; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.Node; |
| /* |
| * Base class for UA model elements. |
| */ |
| public class UAElement implements IUAElement { |
| |
| private static final String ELEMENT_FILTER = "filter"; //$NON-NLS-1$ |
| private static final String ATTRIBUTE_FILTER = "filter"; //$NON-NLS-1$ |
| private static final String ATTRIBUTE_NAME = "name"; //$NON-NLS-1$ |
| private static final String ATTRIBUTE_VALUE = "value"; //$NON-NLS-1$ |
| |
| private static DocumentBuilder builder; |
| private static Document document; |
| |
| private Element element; |
| private UAElement parent; |
| protected List<UAElement> children; |
| private Filter[] filters; |
| private Expression enablementExpression; |
| private IUAElement src; |
| |
| private class Filter { |
| public Filter(String name, String value, boolean isNegated) { |
| this.name = name; |
| this.value = value; |
| this.isNegated = isNegated; |
| } |
| String name; |
| String value; |
| boolean isNegated; |
| } |
| |
| public UAElement(Element element) { |
| this.element = element; |
| } |
| |
| public UAElement(String name) { |
| this.element = getDocument().createElement(name); |
| } |
| |
| public UAElement(String name, IUAElement src) { |
| this(name); |
| if (src instanceof UAElement) { |
| copyFilters(src); |
| } else { |
| this.src = src; |
| } |
| } |
| |
| private void copyFilters(IUAElement src) { |
| UAElement sourceElement = (UAElement)src; |
| String filter = sourceElement.getAttribute(ATTRIBUTE_FILTER); |
| if (filter != null && filter.length() > 0) { |
| this.setAttribute(ATTRIBUTE_FILTER, filter); |
| } |
| filters = sourceElement.getFilterElements(); |
| this.enablementExpression = sourceElement.enablementExpression; |
| this.src = sourceElement.src; |
| } |
| |
| private Filter[] getFilterElements() { |
| if (filters == null) { |
| List<Filter> list = new ArrayList<>(); |
| if (element.hasChildNodes()) { |
| Node node = element.getFirstChild(); |
| while (node != null) { |
| if (node.getNodeType() == Node.ELEMENT_NODE) { |
| String elementKind = node.getNodeName(); |
| if (ExpressionTagNames.ENABLEMENT.equals(elementKind)) { |
| Element enablement = (Element)node; |
| try { |
| enablementExpression = ExpressionConverter.getDefault().perform(enablement); |
| } |
| catch (CoreException e) { |
| |
| } |
| } else if (ELEMENT_FILTER.equals(elementKind)) { |
| Element filter = (Element)node; |
| String filterName = filter.getAttribute(ATTRIBUTE_NAME); |
| String value = filter.getAttribute(ATTRIBUTE_VALUE); |
| if (filterName.length() > 0 && value.length() > 0) { |
| boolean isNegated = false; |
| if (value.startsWith("!")) { //$NON-NLS-1$ |
| isNegated = true; |
| value = value.substring(1); |
| } |
| if (filterName.length() > 0 && value.length() > 0) { |
| list.add(new Filter(filterName, value, isNegated)); |
| } |
| } |
| } |
| } |
| node = node.getNextSibling(); |
| } |
| } |
| filters = list.toArray(new Filter[list.size()]); |
| } |
| return filters; |
| } |
| |
| public void appendChild(UAElement uaElementToAppend) { |
| importElement(uaElementToAppend); |
| element.appendChild(uaElementToAppend.element); |
| uaElementToAppend.parent = this; |
| |
| if (children != null) { |
| children.add(uaElementToAppend); |
| } |
| } |
| |
| public void appendChildren(IUAElement[] children) { |
| if (this.children == null && children.length > 0) { |
| this.children = new ArrayList<>(4); |
| } |
| for (int i=0;i<children.length;i++) { |
| appendChild(children[i] instanceof UAElement ? (UAElement)children[i] : UAElementFactory.newElement(children[i])); |
| } |
| } |
| |
| /* |
| * This method is synchronized to fix Bug 232169. When modifying this source be careful not |
| * to introduce any logic which could possibly cause this thread to block. |
| */ |
| synchronized public String getAttribute(String name) { |
| String value = element.getAttribute(name); |
| if (value != null && value.length() > 0) { |
| return value; |
| } |
| return null; |
| } |
| |
| /* |
| * This method is synchronized to fix Bug 230037. A review of the code indicated that there was no |
| * path which could get blocked and cause deadlock. When modifying this source be careful not |
| * to introduce any logic which could possibly cause this thread to block. |
| */ |
| @Override |
| public synchronized IUAElement[] getChildren() { |
| if (children == null) { |
| if (element.hasChildNodes()) { |
| children = new ArrayList<>(4); |
| Node node = element.getFirstChild(); |
| while (node != null) { |
| if (node.getNodeType() == Node.ELEMENT_NODE) { |
| UAElement uaElement = UAElementFactory.newElement((Element)node); |
| if (uaElement != null) { |
| uaElement.parent = this; |
| children.add(uaElement); |
| } |
| } |
| node = node.getNextSibling(); |
| } |
| } else { |
| return new UAElement[0]; |
| } |
| } |
| return children.toArray(new UAElement[children.size()]); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public <T> T[] getChildren(Class<T> clazz) { |
| IUAElement[] children = getChildren(); |
| if (children.length > 0) { |
| List<Object> list = new ArrayList<>(); |
| for (int i=0;i<children.length;++i) { |
| IUAElement child = children[i]; |
| if (clazz.isAssignableFrom(child.getClass())) { |
| list.add(child); |
| } |
| } |
| return list.toArray((T[]) Array.newInstance(clazz, list.size())); |
| } |
| return (T[]) Array.newInstance(clazz, 0); |
| } |
| |
| public String getElementName() { |
| return element.getNodeName(); |
| } |
| |
| private static Document getDocument() { |
| if (document == null) { |
| if (builder == null) { |
| try { |
| builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); |
| builder.setEntityResolver(new LocalEntityResolver()); |
| } |
| catch (ParserConfigurationException e) { |
| String msg = "Error creating document builder"; //$NON-NLS-1$ |
| HelpPlugin.logError(msg, e); |
| } |
| } |
| document = builder.newDocument(); |
| } |
| return document; |
| } |
| |
| public UAElement getParentElement() { |
| return parent; |
| } |
| |
| public void insertBefore(UAElement newChild, UAElement refChild) { |
| importElement(newChild); |
| element.insertBefore(newChild.element, refChild.element); |
| newChild.parent = this; |
| getChildren(); |
| if (children != null) { |
| int index = children.indexOf(refChild); |
| if (index < 0) { |
| // cache is now invalid |
| children = null; |
| } else { |
| children.add(index, newChild); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isEnabled(IEvaluationContext context) { |
| if (!ProductPreferences.useEnablementFilters()) { |
| return true; |
| } |
| if (src != null) { |
| return src.isEnabled(context); |
| } |
| String filter = getAttribute(ATTRIBUTE_FILTER); |
| if (filter != null) { |
| return isEnabledByFilterAttribute(filter); |
| } |
| Filter[] filterElements = getFilterElements(); |
| for (int i = 0; i < filterElements.length; i++) { |
| if (!isFilterEnabled(filterElements[i])) { |
| return false; |
| } |
| } |
| if (enablementExpression != null) { |
| try { |
| return enablementExpression.evaluate(context) == EvaluationResult.TRUE; |
| } catch (CoreException e) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public void removeChild(UAElement elementToRemove) { |
| |
| element.removeChild(elementToRemove.element); |
| elementToRemove.parent = null; |
| |
| if (children != null) { |
| if (!children.remove(elementToRemove)) { |
| // cache is now invalid |
| children = null; |
| } |
| } |
| } |
| |
| public void setAttribute(String name, String value) { |
| element.setAttribute(name, value); |
| } |
| |
| private void importElement(UAElement uaElementToImport) { |
| Element elementToImport = uaElementToImport.element; |
| Document ownerDocument = element.getOwnerDocument(); |
| if (!ownerDocument.equals(elementToImport.getOwnerDocument()) ) { |
| elementToImport = (Element)ownerDocument.importNode(elementToImport, true); |
| uaElementToImport.children = null; |
| } else { |
| if (elementToImport.getParentNode() != null) { |
| elementToImport = (Element)ownerDocument.importNode(elementToImport, true); |
| uaElementToImport.children = null; |
| } else { |
| } |
| } |
| uaElementToImport.element = elementToImport; |
| } |
| |
| private boolean isEnabledByFilterAttribute(String filter) { |
| return !FilterResolver.getInstance().isFiltered(filter); |
| } |
| |
| private boolean isFilterEnabled(Filter filter) { |
| return !FilterResolver.getInstance().isFiltered(filter.name, filter.value, filter.isNegated); |
| } |
| |
| public Element getElement() { |
| return element; |
| } |
| |
| } |