blob: ef37b0c199d10306c00632a2a1b9b9b805e77247 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}