diff --git a/bundles/org.eclipse.e4.ui.css.core/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.ui.css.core/META-INF/MANIFEST.MF index a911add12a5..d1a4e24ad74 100644 --- a/bundles/org.eclipse.e4.ui.css.core/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.e4.ui.css.core/META-INF/MANIFEST.MF @@ -31,6 +31,7 @@ Export-Package: org.eclipse.e4.ui.css.core;x-internal:=true, org.eclipse.e4.ui.css.core.impl.dom.parsers;x-internal:=true, org.eclipse.e4.ui.css.core.impl.dom.properties;x-friends:="org.eclipse.e4.ui.css.swt", org.eclipse.e4.ui.css.core.impl.engine;x-friends:="org.eclipse.e4.ui.css.swt,org.eclipse.e4.ui.workbench.swt", + org.eclipse.e4.ui.css.core.impl.engine.selector;x-friends:="org.eclipse.e4.ui.tests.css.core", org.eclipse.e4.ui.css.core.impl.sac;x-internal:=true, org.eclipse.e4.ui.css.core.resources;x-friends:="org.eclipse.e4.ui.css.swt,org.eclipse.e4.ui.workbench.renderers.swt", org.eclipse.e4.ui.css.core.sac;x-internal:=true, diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/ExtendedCSSRule.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/ExtendedCSSRule.java index ae6bd4c11f5..1d50bb853ed 100644 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/ExtendedCSSRule.java +++ b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/ExtendedCSSRule.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2013 Angelo Zerr and others. + * Copyright (c) 2008, 2026 Angelo Zerr and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -13,8 +13,7 @@ *******************************************************************************/ package org.eclipse.e4.ui.css.core.dom; -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SelectorList; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors; import org.w3c.dom.css.CSSRule; /** @@ -28,7 +27,7 @@ public interface ExtendedCSSRule extends CSSRule { public CSSPropertyList getCSSPropertyList(); /** - * Return the list of {@link Selector} of this {@link CSSRule}. + * Return the list of selectors of this {@link CSSRule}. */ - public SelectorList getSelectorList(); + public Selectors.SelectorList getSelectorList(); } diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/ExtendedDocumentCSS.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/ExtendedDocumentCSS.java index f23d30d74cf..d3d7bc717d1 100644 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/ExtendedDocumentCSS.java +++ b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/ExtendedDocumentCSS.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2018 Angelo Zerr and others. + * Copyright (c) 2008, 2026 Angelo Zerr and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -15,9 +15,6 @@ package org.eclipse.e4.ui.css.core.dom; import java.util.EventListener; -import java.util.List; -import org.w3c.css.sac.Condition; -import org.w3c.css.sac.Selector; import org.w3c.dom.css.DocumentCSS; import org.w3c.dom.stylesheets.StyleSheet; @@ -26,21 +23,10 @@ */ public interface ExtendedDocumentCSS extends DocumentCSS { - public static final Integer SAC_ID_CONDITION = Integer.valueOf(Condition.SAC_ID_CONDITION); - public static final Integer SAC_CLASS_CONDITION = Integer.valueOf(Condition.SAC_CLASS_CONDITION); - public static final Integer SAC_PSEUDO_CLASS_CONDITION = Integer.valueOf(Condition.SAC_PSEUDO_CLASS_CONDITION); - public static final Integer OTHER_SAC_CONDITIONAL_SELECTOR = Integer.valueOf(Selector.SAC_CONDITIONAL_SELECTOR); - - public static final Integer OTHER_SAC_SELECTOR = Integer.valueOf(999); - public void addStyleSheet(StyleSheet styleSheet); public void removeAllStyleSheets(); - public List queryConditionSelector(int conditionType); - - public List querySelector(int selectorType, int conditionType); - /** * @since 0.12.200 */ diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/properties/ICSSPropertyHandler.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/properties/ICSSPropertyHandler.java index c7cef57e669..c7279da95be 100644 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/properties/ICSSPropertyHandler.java +++ b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/properties/ICSSPropertyHandler.java @@ -63,4 +63,23 @@ public default String retrieveCSSProperty(Object element, String property, Strin return null; } + /** + * Callback method called once after all CSS properties of a single + * declaration have been applied. Handlers that need to perform a final + * step (re-layout, redraw, batched commit, ...) override this method; + * the default is a no-op. + */ + default void onAllCSSPropertiesApplied(Object element, CSSEngine engine) throws Exception { + // do nothing + } + + /** + * Variant of {@link #onAllCSSPropertiesApplied(Object, CSSEngine)} that + * also receives the pseudo class. Defaults to delegating to the + * pseudo-less form. + */ + default void onAllCSSPropertiesApplied(Object element, CSSEngine engine, String pseudo) throws Exception { + onAllCSSPropertiesApplied(element, engine); + } + } diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/properties/ICSSPropertyHandler2.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/properties/ICSSPropertyHandler2.java deleted file mode 100644 index 7461186491f..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/properties/ICSSPropertyHandler2.java +++ /dev/null @@ -1,39 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008, 2015 Angelo Zerr 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: - * Angelo Zerr - initial API and implementation - *******************************************************************************/ -package org.eclipse.e4.ui.css.core.dom.properties; - -import org.eclipse.e4.ui.css.core.engine.CSSEngine; - -/** - * CSS Property Handler to intercept when all CSS Properties are applied. - * - * @version 1.0.0 - * @author Angelo ZERR - */ -public interface ICSSPropertyHandler2 { - - /** - * Callback method called when all CSS properties are applied. - */ - default void onAllCSSPropertiesApplyed(Object element, CSSEngine engine) throws Exception { - // do nothing - } - - /** - * Callback method called when all CSS properties are applied. - */ - default void onAllCSSPropertiesApplyed(Object element, CSSEngine engine, String pseudo) throws Exception { - onAllCSSPropertiesApplyed(element, engine); - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/properties/ICSSPropertyHandler2Delegate.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/properties/ICSSPropertyHandler2Delegate.java deleted file mode 100644 index db26da9c36e..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/dom/properties/ICSSPropertyHandler2Delegate.java +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008, 2013 Angelo Zerr 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: - * Angelo Zerr - initial API and implementation - *******************************************************************************/ -package org.eclipse.e4.ui.css.core.dom.properties; - -/** - * - * {@link ICSSPropertyHandler2} delegate. - * - * @version 1.0.0 - * @author Angelo ZERR - */ -public interface ICSSPropertyHandler2Delegate { - - /** - * Return {@link ICSSPropertyHandler2} to call when all CSS Properties are - * applied . - */ - public ICSSPropertyHandler2 getCSSPropertyHandler2(); -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/engine/CSSEngine.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/engine/CSSEngine.java index a6624b84852..f036cf16154 100644 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/engine/CSSEngine.java +++ b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/engine/CSSEngine.java @@ -20,11 +20,10 @@ import org.eclipse.e4.ui.css.core.dom.IElementProvider; import org.eclipse.e4.ui.css.core.dom.properties.ICSSPropertyHandler; import org.eclipse.e4.ui.css.core.dom.properties.converters.ICSSValueConverter; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors; import org.eclipse.e4.ui.css.core.resources.IResourcesRegistry; import org.eclipse.e4.ui.css.core.util.resources.IResourcesLocatorManager; import org.w3c.css.sac.InputSource; -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SelectorList; import org.w3c.dom.Element; import org.w3c.dom.css.CSSStyleDeclaration; import org.w3c.dom.css.CSSStyleSheet; @@ -105,27 +104,27 @@ public interface CSSEngine { /** * Parse Selectors from String value. */ - SelectorList parseSelectors(String text); + Selectors.SelectorList parseSelectors(String text); /** * Parse Selectors from InputSource value. */ - SelectorList parseSelectors(InputSource source) throws IOException; + Selectors.SelectorList parseSelectors(InputSource source) throws IOException; /** * Parse Selectors from InputStream. */ - SelectorList parseSelectors(InputStream stream) throws IOException; + Selectors.SelectorList parseSelectors(InputStream stream) throws IOException; /** * Parse Selectors from String value. */ - SelectorList parseSelectors(Reader reader) throws IOException; + Selectors.SelectorList parseSelectors(Reader reader) throws IOException; /** * Check if the selector matches the object node. */ - boolean matches(Selector selector, Object node, String pseudo); + boolean matches(Selectors.Selector selector, Object node, String pseudo); /*--------------- Apply styles -----------------*/ diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/dom/CSSStyleRuleImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/dom/CSSStyleRuleImpl.java index 01e5822bd4a..23200bae6c2 100644 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/dom/CSSStyleRuleImpl.java +++ b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/dom/CSSStyleRuleImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2015 Angelo Zerr and others. + * Copyright (c) 2008, 2026 Angelo Zerr and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -17,8 +17,7 @@ import org.eclipse.e4.ui.css.core.dom.CSSPropertyList; import org.eclipse.e4.ui.css.core.dom.ExtendedCSSRule; -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SelectorList; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors; import org.w3c.dom.DOMException; import org.w3c.dom.css.CSSRule; import org.w3c.dom.css.CSSStyleDeclaration; @@ -27,10 +26,10 @@ public class CSSStyleRuleImpl extends CSSRuleImpl implements CSSStyleRule, ExtendedCSSRule { - private final SelectorList selectors; + private final Selectors.SelectorList selectors; private CSSStyleDeclaration styleDeclaration; - public CSSStyleRuleImpl(CSSStyleSheet parentStyleSheet, CSSRule parentRule, SelectorList selectors) { + public CSSStyleRuleImpl(CSSStyleSheet parentStyleSheet, CSSRule parentRule, Selectors.SelectorList selectors) { super(parentStyleSheet, parentRule); this.selectors = selectors; } @@ -55,17 +54,7 @@ public String getCssText() { @Override public String getSelectorText() { - StringBuilder sb = new StringBuilder(); - for (int selID = 0; selID < getSelectorList().getLength(); selID++) { - Selector item = getSelectorList().item(selID); - sb.append(item.toString()); - sb.append(", "); - } - if (getSelectorList().getLength() > 0) { - sb.delete(sb.length() - 2, sb.length()); - } - - return sb.toString(); + return selectors.text(); } @Override @@ -83,7 +72,7 @@ public void setSelectorText(String selectorText) throws DOMException { // Additional methods @Override - public SelectorList getSelectorList() { + public Selectors.SelectorList getSelectorList() { return selectors; } diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/dom/DocumentCSSImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/dom/DocumentCSSImpl.java index f9fba83d961..b87b42b143c 100644 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/dom/DocumentCSSImpl.java +++ b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/dom/DocumentCSSImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2018 Angelo Zerr and others. + * Copyright (c) 2008, 2026 Angelo Zerr and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -18,19 +18,10 @@ package org.eclipse.e4.ui.css.core.impl.dom; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import org.eclipse.e4.ui.css.core.dom.ExtendedCSSRule; import org.eclipse.e4.ui.css.core.dom.ExtendedDocumentCSS; -import org.w3c.css.sac.ConditionalSelector; -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SelectorList; import org.w3c.dom.Element; -import org.w3c.dom.css.CSSRule; -import org.w3c.dom.css.CSSRuleList; import org.w3c.dom.css.CSSStyleDeclaration; -import org.w3c.dom.css.CSSStyleSheet; import org.w3c.dom.css.DocumentCSS; import org.w3c.dom.stylesheets.StyleSheet; import org.w3c.dom.stylesheets.StyleSheetList; @@ -42,11 +33,6 @@ public class DocumentCSSImpl implements ExtendedDocumentCSS { private final StyleSheetListImpl styleSheetList = new StyleSheetListImpl(); - /** - * key=selector type, value = CSSStyleDeclaration - */ - private Map> styleDeclarationMap; - private final List styleSheetChangeListeners = new ArrayList<>(1); @Override @@ -72,93 +58,6 @@ public void removeAllStyleSheets() { styleSheetChangeListeners.forEach(l -> l.styleSheetRemoved(styleSheet)); } styleSheetList.removeAllStyleSheets(); - this.styleDeclarationMap = null; - } - - @Override - public List queryConditionSelector(int conditionType) { - return querySelector(Selector.SAC_CONDITIONAL_SELECTOR, conditionType); - } - - @Override - public List querySelector(int selectorType, int conditionType) { - List list = getCSSStyleDeclarationList(selectorType, conditionType); - if (list != null) { - return list; - } - int l = styleSheetList.getLength(); - for (int i = 0; i < l; i++) { - CSSStyleSheet styleSheet = (CSSStyleSheet) styleSheetList.item(i); - CSSRuleList ruleList = styleSheet.getCssRules(); - list = querySelector(ruleList, selectorType, conditionType); - setCSSStyleDeclarationList(list, selectorType, conditionType); - } - return list; - } - - protected List querySelector(CSSRuleList ruleList, int selectorType, int selectorConditionType) { - List list = new ArrayList<>(); - if (selectorType == Selector.SAC_CONDITIONAL_SELECTOR) { - int length = ruleList.getLength(); - for (int i = 0; i < length; i++) { - CSSRule rule = ruleList.item(i); - if (rule.getType() == CSSRule.STYLE_RULE && rule instanceof ExtendedCSSRule r) { - SelectorList selectorList = r.getSelectorList(); - // Loop for SelectorList - int l = selectorList.getLength(); - for (int j = 0; j < l; j++) { - Selector selector = selectorList.item(j); - if (selector.getSelectorType() == selectorType) { - // It's conditional selector - ConditionalSelector conditionalSelector = (ConditionalSelector) selector; - short conditionType = conditionalSelector.getCondition().getConditionType(); - if (selectorConditionType == conditionType) { - // current selector match the current CSS - // Rule - // CSSStyleRule styleRule = (CSSStyleRule) - // rule; - list.add(selector); - } - } - } - } - } - } - return list; - } - - protected List getCSSStyleDeclarationList(int selectorType, int conditionType) { - Integer key = getKey(selectorType, conditionType); - return getStyleDeclarationMap().get(key); - } - - protected void setCSSStyleDeclarationList(List list, int selectorType, int conditionType) { - Integer key = getKey(selectorType, conditionType); - getStyleDeclarationMap().put(key, list); - } - - protected Integer getKey(int selectorType, int conditionType) { - if (selectorType == Selector.SAC_CONDITIONAL_SELECTOR) { - if (conditionType == SAC_CLASS_CONDITION.intValue()) { - return SAC_CLASS_CONDITION; - } - if (conditionType == SAC_ID_CONDITION.intValue()) { - return SAC_ID_CONDITION; - } - if (conditionType == SAC_PSEUDO_CLASS_CONDITION.intValue()) { - return SAC_PSEUDO_CLASS_CONDITION; - } - return OTHER_SAC_CONDITIONAL_SELECTOR; - } - - return OTHER_SAC_SELECTOR; - } - - protected Map> getStyleDeclarationMap() { - if (styleDeclarationMap == null) { - styleDeclarationMap = new HashMap<>(); - } - return styleDeclarationMap; } @Override diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/dom/ViewCSSImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/dom/ViewCSSImpl.java index 5fc2ce9e605..acac122a5c3 100644 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/dom/ViewCSSImpl.java +++ b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/dom/ViewCSSImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2018 Angelo Zerr and others. + * Copyright (c) 2008, 2026 Angelo Zerr and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -21,11 +21,9 @@ import java.util.List; import org.eclipse.e4.ui.css.core.dom.ExtendedCSSRule; import org.eclipse.e4.ui.css.core.dom.ExtendedDocumentCSS; -import org.eclipse.e4.ui.css.core.impl.sac.ExtendedSelector; -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SelectorList; +import org.eclipse.e4.ui.css.core.impl.engine.selector.SelectorMatcher; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors; import org.w3c.dom.Element; -import org.w3c.dom.Node; import org.w3c.dom.css.CSSRule; import org.w3c.dom.css.CSSRuleList; import org.w3c.dom.css.CSSStyleDeclaration; @@ -112,17 +110,6 @@ private List getCombinedRules() { } private CSSStyleDeclaration getComputedStyle(List ruleList, Element elt, String pseudoElt) { - Node parent = elt.getParentNode(); - - Node[] hierarchy = null; - if (parent != null) { - List hierarchyList = new ArrayList<>(); - for (Node n = parent; n != null; n = n.getParentNode()) { - hierarchyList.add(n); - } - hierarchy = hierarchyList.toArray(new Node[hierarchyList.size()]); - } - List styleDeclarations = null; StyleWrapper firstStyleDeclaration = null; int position = 0; @@ -132,27 +119,22 @@ private CSSStyleDeclaration getComputedStyle(List ruleList, Element elt } CSSStyleRule styleRule = (CSSStyleRule) rule; ExtendedCSSRule r = (ExtendedCSSRule) rule; - SelectorList selectorList = r.getSelectorList(); - // Loop for SelectorList - int l = selectorList.getLength(); - for (int j = 0; j < l; j++) { - Selector selector = selectorList.item(j); - if (selector instanceof ExtendedSelector extendedSelector) { - if (extendedSelector.match(elt, hierarchy, 0, pseudoElt)) { - CSSStyleDeclaration style = styleRule.getStyle(); - int specificity = extendedSelector.getSpecificity(); - StyleWrapper wrapper = new StyleWrapper(style, specificity, position++); - if (firstStyleDeclaration == null) { - firstStyleDeclaration = wrapper; - } else { - // There is several Style Declarations which - // match the current element - if (styleDeclarations == null) { - styleDeclarations = new ArrayList<>(); - styleDeclarations.add(firstStyleDeclaration); - } - styleDeclarations.add(wrapper); + Selectors.SelectorList selectorList = r.getSelectorList(); + for (Selectors.Selector selector : selectorList.alternatives()) { + if (SelectorMatcher.matches(selector, elt, pseudoElt)) { + CSSStyleDeclaration style = styleRule.getStyle(); + int specificity = selector.specificity(); + StyleWrapper wrapper = new StyleWrapper(style, specificity, position++); + if (firstStyleDeclaration == null) { + firstStyleDeclaration = wrapper; + } else { + // There is several Style Declarations which + // match the current element + if (styleDeclarations == null) { + styleDeclarations = new ArrayList<>(); + styleDeclarations.add(firstStyleDeclaration); } + styleDeclarations.add(wrapper); } } } diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/AbstractCSSEngine.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/AbstractCSSEngine.java deleted file mode 100644 index 66823482a9c..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/AbstractCSSEngine.java +++ /dev/null @@ -1,1122 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008, 2020 Angelo Zerr 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: - * Angelo Zerr - initial API and implementation - * IBM Corporation - ongoing development - * Red Hat Inc. (mistria) - Fixes suggested by FindBugs - * Red Hat Inc. (mistria) - Bug 413348: fix stream leak - * Lars Vogel - Bug 428715 - * Brian de Alwis (MTI) - Performance tweaks (Bug 430829) - * Dirk Fauth - Bug 479896 - * Patrik Suzzi - Bug 500402 - * Daniel Raap - Bug 511836 - *******************************************************************************/ -package org.eclipse.e4.ui.css.core.impl.engine; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.io.StringReader; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.BiConsumer; -import org.eclipse.core.runtime.FileLocator; -import org.eclipse.core.runtime.IPath; -import org.eclipse.e4.ui.css.core.dom.CSSStylableElement; -import org.eclipse.e4.ui.css.core.dom.ChildVisibilityAwareElement; -import org.eclipse.e4.ui.css.core.dom.ExtendedCSSRule; -import org.eclipse.e4.ui.css.core.dom.ExtendedDocumentCSS; -import org.eclipse.e4.ui.css.core.dom.IElementProvider; -import org.eclipse.e4.ui.css.core.dom.IStreamingNodeList; -import org.eclipse.e4.ui.css.core.dom.parsers.CSSParser; -import org.eclipse.e4.ui.css.core.dom.properties.ICSSPropertyCompositeHandler; -import org.eclipse.e4.ui.css.core.dom.properties.ICSSPropertyHandler; -import org.eclipse.e4.ui.css.core.dom.properties.ICSSPropertyHandler2; -import org.eclipse.e4.ui.css.core.dom.properties.ICSSPropertyHandler2Delegate; -import org.eclipse.e4.ui.css.core.dom.properties.ICSSPropertyHandlerProvider; -import org.eclipse.e4.ui.css.core.dom.properties.converters.ICSSValueConverter; -import org.eclipse.e4.ui.css.core.engine.CSSElementContext; -import org.eclipse.e4.ui.css.core.engine.CSSEngine; -import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler; -import org.eclipse.e4.ui.css.core.exceptions.UnsupportedPropertyException; -import org.eclipse.e4.ui.css.core.impl.dom.CSSRuleListImpl; -import org.eclipse.e4.ui.css.core.impl.dom.CSSStyleSheetImpl; -import org.eclipse.e4.ui.css.core.impl.dom.DocumentCSSImpl; -import org.eclipse.e4.ui.css.core.impl.dom.ViewCSSImpl; -import org.eclipse.e4.ui.css.core.impl.sac.ExtendedSelector; -import org.eclipse.e4.ui.css.core.resources.IResourcesRegistry; -import org.eclipse.e4.ui.css.core.resources.ResourceRegistryKeyFactory; -import org.eclipse.e4.ui.css.core.util.impl.resources.ResourcesLocatorManager; -import org.eclipse.e4.ui.css.core.util.resources.IResourcesLocatorManager; -import org.eclipse.e4.ui.css.core.utils.StringUtils; -import org.w3c.css.sac.AttributeCondition; -import org.w3c.css.sac.CombinatorCondition; -import org.w3c.css.sac.Condition; -import org.w3c.css.sac.ConditionalSelector; -import org.w3c.css.sac.DescendantSelector; -import org.w3c.css.sac.InputSource; -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SelectorList; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.w3c.dom.css.CSSImportRule; -import org.w3c.dom.css.CSSRule; -import org.w3c.dom.css.CSSRuleList; -import org.w3c.dom.css.CSSStyleDeclaration; -import org.w3c.dom.css.CSSStyleSheet; -import org.w3c.dom.css.CSSValue; -import org.w3c.dom.css.DocumentCSS; -import org.w3c.dom.css.ViewCSS; -import org.w3c.dom.stylesheets.StyleSheet; - -/** - * Abstract CSS Engine manage style sheet parsing and store the - * {@link CSSStyleSheet} into {@link DocumentCSS}. - * - * To apply styles, call the {@link #applyStyles(Object, boolean, boolean)} - * method. This method check if {@link ICSSPropertyHandler} is registered for - * apply the CSS property. - * - * @version 1.0.0 - * @author Angelo ZERR - */ -public abstract class AbstractCSSEngine implements CSSEngine { - - /** - * Archives are deliberately identified by exclamation mark in URLs - */ - private static final String ARCHIVE_IDENTIFIER = "!"; - - /** - * Default {@link IResourcesLocatorManager} used to get InputStream, Reader - * resource like Image. - */ - private static final IResourcesLocatorManager defaultResourcesLocatorManager = ResourcesLocatorManager.INSTANCE; - - /** - * w3c {@link DocumentCSS}. - */ - private final ExtendedDocumentCSS documentCSS; - - /** - * w3c {@link ViewCSS}. - */ - private final ViewCSS viewCSS; - - /** - * {@link IElementProvider} used to retrieve w3c Element linked to the - * widget. - */ - private IElementProvider elementProvider; - - protected boolean computeDefaultStyle = false; - - private Map elementsContext = null; - - /** - * CSS Error Handler to intercept error while parsing, applying styles. - */ - private CSSErrorHandler errorHandler; - - private IResourcesLocatorManager resourcesLocatorManager; - - private IResourcesRegistry resourcesRegistry; - - /** - * An ordered list of ICSSPropertyHandlerProvider - */ - protected List propertyHandlerProviders = new ArrayList<>(); - // for performance hold a map of handlers to singleton list - private final Map> propertyHandler2InstanceMap = new HashMap<>(); - - private Map currentCSSPropertiesApplied; - - private boolean throwError; - - private Map valueConverters = null; - - private int parseImport; - - private ResourceRegistryKeyFactory keyFactory; - - public AbstractCSSEngine() { - this(new DocumentCSSImpl()); - } - - public AbstractCSSEngine(ExtendedDocumentCSS documentCSS) { - this.documentCSS = documentCSS; - this.viewCSS = new ViewCSSImpl(documentCSS); - keyFactory = new ResourceRegistryKeyFactory(); - } - - /*--------------- Parse style sheet -----------------*/ - - @Override - public StyleSheet parseStyleSheet(Reader reader) throws IOException { - InputSource source = new InputSource(); - source.setCharacterStream(reader); - return parseStyleSheet(source); - } - - @Override - public StyleSheet parseStyleSheet(InputStream stream) throws IOException { - InputSource source = new InputSource(); - source.setByteStream(stream); - return parseStyleSheet(source); - } - - @Override - public StyleSheet parseStyleSheet(InputSource source) throws IOException { - // Check that CharacterStream or ByteStream is not null - checkInputSource(source); - CSSParser parser = makeCSSParser(); - CSSStyleSheet styleSheet = parser.parseStyleSheet(source); - - CSSRuleList rules = styleSheet.getCssRules(); - int length = rules.getLength(); - CSSRuleListImpl masterList = new CSSRuleListImpl(); - int counter; - for (counter = 0; counter < length; counter++) { - CSSRule rule = rules.item(counter); - if (rule.getType() != CSSRule.IMPORT_RULE) { - break; - } - // processing an import CSS - CSSImportRule importRule = (CSSImportRule) rule; - URL url = null; - if (importRule.getHref().startsWith("platform")) { - url = FileLocator.resolve(new URL(importRule.getHref())); - } else { - IPath p = IPath.fromOSString(source.getURI()); - IPath trim = p.removeLastSegments(1); - boolean isArchive = source.getURI().contains(ARCHIVE_IDENTIFIER); - url = FileLocator - .resolve(new URL(trim.addTrailingSeparator().toString() + ((CSSImportRule) rule).getHref())); - File testFile = new File(url.getFile()); - if (!isArchive&&!testFile.exists()) { - // look in platform default - String path = getResourcesLocatorManager().resolve((importRule).getHref()); - testFile = new File(new URL(path).getFile()); - if (testFile.exists()) { - url = new URL(path); - } - } - } - try (InputStream stream = url.openStream()) { - InputSource tempStream = new InputSource(); - tempStream.setURI(url.toString()); - tempStream.setByteStream(stream); - parseImport++; - try { - styleSheet = (CSSStyleSheet) this.parseStyleSheet(tempStream); - } finally { - parseImport--; - } - CSSRuleList tempRules = styleSheet.getCssRules(); - for (int j = 0; j < tempRules.getLength(); j++) { - masterList.add(tempRules.item(j)); - } - } - } - - // add remaining non import rules - for (int i = counter; i < length; i++) { - masterList.add(rules.item(i)); - } - - // final stylesheet - CSSStyleSheetImpl s = new CSSStyleSheetImpl(); - s.setRuleList(masterList); - if (parseImport == 0) { - documentCSS.addStyleSheet(s); - } - return s; - } - - private void processNodeList(NodeList nodes, BiConsumer consumer, boolean applyStylesToChildNodes) { - if (nodes instanceof IStreamingNodeList) { - ((IStreamingNodeList) nodes).stream().forEach(child -> { - consumer.accept(child, applyStylesToChildNodes); - }); - } else { - int length = nodes.getLength(); - for (int k = 0; k < length; k++) { - consumer.accept(nodes.item(k), applyStylesToChildNodes); - } - } - } - - /** - * Return true if source is valid and false otherwise. - */ - private void checkInputSource(InputSource source) throws IOException { - Reader reader = source.getCharacterStream(); - InputStream stream = source.getByteStream(); - if (reader == null && stream == null) { - throw new IOException( - "CharacterStream or ByteStream cannot be null for the InputSource."); - } - } - - /*--------------- Parse style declaration -----------------*/ - - @Override - public CSSStyleDeclaration parseStyleDeclaration(String style) { - try { - return parseStyleDeclaration(new StringReader(style)); - } catch (IOException e) { - throw new IllegalStateException("StringReader cannot throw IOException", e); //$NON-NLS-1$ - } - } - - @Override - public CSSStyleDeclaration parseStyleDeclaration(Reader reader) throws IOException { - InputSource source = new InputSource(); - source.setCharacterStream(reader); - return parseStyleDeclaration(source); - } - - @Override - public CSSStyleDeclaration parseStyleDeclaration(InputStream stream) throws IOException { - InputSource source = new InputSource(); - source.setByteStream(stream); - return parseStyleDeclaration(source); - } - - @Override - public CSSStyleDeclaration parseStyleDeclaration(InputSource source) throws IOException { - checkInputSource(source); - CSSParser parser = makeCSSParser(); - return parser.parseStyleDeclaration(source); - } - - /*--------------- Parse CSS Selector -----------------*/ - - @Override - public SelectorList parseSelectors(String selector) { - try { - return parseSelectors(new StringReader(selector)); - } catch (IOException e) { - throw new IllegalStateException("StringReader cannot throw IOException", e); //$NON-NLS-1$ - } - } - - @Override - public SelectorList parseSelectors(Reader reader) throws IOException { - InputSource source = new InputSource(); - source.setCharacterStream(reader); - return parseSelectors(source); - } - - @Override - public SelectorList parseSelectors(InputStream stream) throws IOException { - InputSource source = new InputSource(); - source.setByteStream(stream); - return parseSelectors(source); - } - - @Override - public SelectorList parseSelectors(InputSource source) throws IOException { - checkInputSource(source); - CSSParser parser = makeCSSParser(); - return parser.parseSelectors(source); - } - - /*--------------- Parse CSS Property Value-----------------*/ - - @Override - public CSSValue parsePropertyValue(Reader reader) throws IOException { - InputSource source = new InputSource(); - source.setCharacterStream(reader); - return parsePropertyValue(source); - } - - @Override - public CSSValue parsePropertyValue(InputStream stream) throws IOException { - InputSource source = new InputSource(); - source.setByteStream(stream); - return parsePropertyValue(source); - } - - @Override - public CSSValue parsePropertyValue(String value) { - try { - return parsePropertyValue(new StringReader(value)); - } catch (IOException e) { - throw new IllegalStateException("StringReader cannot throw IOException", e); //$NON-NLS-1$ - } - } - - @Override - public CSSValue parsePropertyValue(InputSource source) throws IOException { - checkInputSource(source); - CSSParser parser = makeCSSParser(); - return parser.parsePropertyValue(source); - } - - /*--------------- Apply styles -----------------*/ - - @Override - public void applyStyles(Object element, boolean applyStylesToChildNodes) { - applyStyles(element, applyStylesToChildNodes, computeDefaultStyle); - } - - @Override - public void applyStyles(Object element, boolean applyStylesToChildNodes, boolean computeDefaultStyle) { - Element elt = getElement(element); - if (elt == null || !isVisible(elt)) { - return; - } - - /* - * Compute new Style to apply. - */ - CSSStyleDeclaration style = viewCSS.getComputedStyle(elt, null); - if (computeDefaultStyle) { - if (applyStylesToChildNodes) { - this.computeDefaultStyle = computeDefaultStyle; - } - /* - * Apply default style. - */ - applyDefaultStyleDeclaration(element, false, style, null); - } - - /* - * Manage static pseudo instances - */ - String[] pseudoInstances = getStaticPseudoInstances(elt); - if (pseudoInstances != null && pseudoInstances.length > 0) { - // there are static pseudo instances defined, loop for it and - // apply styles for each pseudo instance. - for (String pseudoInstance : pseudoInstances) { - CSSStyleDeclaration styleWithPseudoInstance = viewCSS.getComputedStyle(elt, pseudoInstance); - if (computeDefaultStyle) { - /* - * Apply default style for the current pseudo instance. - */ - applyDefaultStyleDeclaration(element, false, styleWithPseudoInstance, pseudoInstance); - } - - if (styleWithPseudoInstance != null) { - CSSRule parentRule = styleWithPseudoInstance.getParentRule(); - if (parentRule instanceof ExtendedCSSRule) { - applyConditionalPseudoStyle((ExtendedCSSRule) parentRule, pseudoInstance, element, styleWithPseudoInstance); - } else { - applyStyleDeclaration(elt, styleWithPseudoInstance, pseudoInstance); - } - } - } - } - - if (style != null) { - applyStyleDeclaration(elt, style, null); - } - try { - // Apply inline style - applyInlineStyle(elt, false); - } catch (Exception e) { - handleExceptions(e); - } - - if (applyStylesToChildNodes) { - /* - * Style all children recursive. - */ - NodeList nodes = elt instanceof ChildVisibilityAwareElement c - ? c.getVisibleChildNodes() - : elt.getChildNodes(); - if (nodes != null) { - processNodeList(nodes, this::applyStyles, applyStylesToChildNodes); - onStylesAppliedToChildNodes(elt, nodes); - } - } - } - - /** - * Allow the CSS engine to skip particular elements if they are not visible. - * Elements need to be restyled when they become visible. - * - * @return true if the element is visible, false if not visible. - */ - protected boolean isVisible(Element elt) { - Node parentNode = elt.getParentNode(); - if (parentNode instanceof ChildVisibilityAwareElement) { - NodeList l = ((ChildVisibilityAwareElement) parentNode).getVisibleChildNodes(); - if (l != null) { - if (l instanceof IStreamingNodeList) { - return ((IStreamingNodeList) l).stream().anyMatch(node -> node == elt); - } else { - int length = l.getLength(); - for (int i = 0; i < length; i++) { - if (l.item(i) == elt) { - return true; - } - } - } - } - return false; - } - return true; - } - - private void applyConditionalPseudoStyle(ExtendedCSSRule parentRule, String pseudoInstance, Object element, CSSStyleDeclaration styleWithPseudoInstance) { - SelectorList selectorList = parentRule.getSelectorList(); - for (int j = 0; j < selectorList.getLength(); j++) { - Selector item = selectorList.item(j); - // search for conditional selectors - ConditionalSelector conditional = null; - if (item instanceof ConditionalSelector) { - conditional = (ConditionalSelector) item; - } else if (item instanceof DescendantSelector) { - if (((DescendantSelector) item).getSimpleSelector() instanceof ConditionalSelector) { - conditional = (ConditionalSelector) ((DescendantSelector) item).getSimpleSelector(); - } else if (((DescendantSelector) item).getAncestorSelector() instanceof ConditionalSelector) { - conditional = (ConditionalSelector) ((DescendantSelector) item).getAncestorSelector(); - } - } - if (conditional != null) { - Condition condition = conditional.getCondition(); - // we're only interested in attribute selector conditions - AttributeCondition attr = null; - if (condition instanceof AttributeCondition) { - attr = (AttributeCondition) condition; - } else if (condition instanceof CombinatorCondition) { - if (((CombinatorCondition) condition).getSecondCondition() instanceof AttributeCondition) { - attr = (AttributeCondition) ((CombinatorCondition) condition).getSecondCondition(); - } else if (((CombinatorCondition) condition).getFirstCondition() instanceof AttributeCondition) { - attr = (AttributeCondition) ((CombinatorCondition) condition).getFirstCondition(); - } - } - if (attr != null) { - String value = attr.getValue(); - if (value.equals(pseudoInstance)) { - // if we match the pseudo, apply the style - applyStyleDeclaration(element, styleWithPseudoInstance, pseudoInstance); - return; - } - } - } - } - } - - protected String[] getStaticPseudoInstances(Element element) { - if (element instanceof CSSStylableElement stylableElement) { - return stylableElement.getStaticPseudoInstances(); - } - return null; - } - - /** - * Callback method called when styles applied of nodes - * children of the element. - */ - protected void onStylesAppliedToChildNodes(Element element, NodeList nodes) { - if (element instanceof CSSStylableElement) { - ((CSSStylableElement) element).onStylesApplied(nodes); - } - } - - /*--------------- Apply style declaration -----------------*/ - - @Override - public void applyStyleDeclaration(Object element, CSSStyleDeclaration style, String pseudo) { - // Apply style - boolean avoidanceCacheInstalled = currentCSSPropertiesApplied == null; - if (avoidanceCacheInstalled) { - currentCSSPropertiesApplied = new HashMap<>(); - } - List handlers2 = Collections.emptyList(); - for (int i = 0; i < style.getLength(); i++) { - String property = style.item(i); - CSSValue value = style.getPropertyCSSValue(property); - try { - ICSSPropertyHandler handler = this.applyCSSProperty(element, property, value, pseudo); - ICSSPropertyHandler2 propertyHandler2 = null; - if (handler instanceof ICSSPropertyHandler2) { - propertyHandler2 = (ICSSPropertyHandler2) handler; - } else if (handler instanceof ICSSPropertyHandler2Delegate) { - propertyHandler2 = ((ICSSPropertyHandler2Delegate) handler).getCSSPropertyHandler2(); - } - if (propertyHandler2 != null) { - switch (handlers2.size()) { - case 0: - handlers2 = propertyHandler2InstanceMap.computeIfAbsent(propertyHandler2, - Collections::singletonList); - break; - case 1: - handlers2 = new ArrayList<>(handlers2); - handlers2.add(propertyHandler2); - break; - default: - if (!handlers2.contains(propertyHandler2)) { - handlers2.add(propertyHandler2); - } - } - } - } catch (Exception e) { - if (throwError || (!throwError && !(e instanceof UnsupportedPropertyException))) { - handleExceptions(e); - } - } - } - for (ICSSPropertyHandler2 handler2 : handlers2) { - try { - handler2.onAllCSSPropertiesApplyed(element, this, pseudo); - } catch (Exception e) { - handleExceptions(e); - } - } - if (avoidanceCacheInstalled) { - currentCSSPropertiesApplied = null; - } - - } - - @Override - public CSSStyleDeclaration parseAndApplyStyleDeclaration(Object node, - Reader reader) throws IOException { - CSSStyleDeclaration style = parseStyleDeclaration(reader); - this.applyStyleDeclaration(node, style, null); - return style; - } - - @Override - public CSSStyleDeclaration parseAndApplyStyleDeclaration(Object node, InputStream stream) throws IOException { - CSSStyleDeclaration style = parseStyleDeclaration(stream); - this.applyStyleDeclaration(node, style, null); - return style; - } - - @Override - public CSSStyleDeclaration parseAndApplyStyleDeclaration(Object node, InputSource source) throws IOException { - CSSStyleDeclaration style = parseStyleDeclaration(source); - this.applyStyleDeclaration(node, style, null); - return style; - } - - @Override - public CSSStyleDeclaration parseAndApplyStyleDeclaration(Object node, String style) throws IOException { - CSSStyleDeclaration styleDeclaration = parseStyleDeclaration(style); - this.applyStyleDeclaration(node, styleDeclaration, null); - return styleDeclaration; - } - - /*--------------- Apply inline style -----------------*/ - - @Override - public void applyInlineStyle(Object node, boolean applyStylesToChildNodes) { - Element elt = getElement(node); - if (elt != null) { - if (elt instanceof CSSStylableElement stylableElement) { - String style = stylableElement.getCSSStyle(); - if (style != null && style.length() > 0) { - try { - parseAndApplyStyleDeclaration(stylableElement.getNativeWidget(), style); - } catch (IOException e) { - handleExceptions(e); - } - } - } - if (applyStylesToChildNodes) { - /* - * Style all children recursive. - */ - NodeList nodes = elt.getChildNodes(); - if (nodes != null) { - processNodeList(nodes, this::applyInlineStyle, applyStylesToChildNodes); - } - } - } - } - - /*--------------- Initial Style -----------------*/ - - @Override - public CSSStyleDeclaration getDefaultStyleDeclaration(Object element, String pseudoE) { - return getDefaultStyleDeclaration(element, null, pseudoE); - } - - public CSSStyleDeclaration getDefaultStyleDeclaration(Object widget, CSSStyleDeclaration newStyle, String pseudoE) { - CSSStyleDeclaration style = null; - for (ICSSPropertyHandlerProvider provider : propertyHandlerProviders) { - try { - style = provider.getDefaultCSSStyleDeclaration(this, widget, newStyle, pseudoE); - } catch (Exception e) { - handleExceptions(e); - } - } - return style; - } - - @Override - public void applyDefaultStyleDeclaration(Object element, boolean applyStylesToChildNodes) { - applyDefaultStyleDeclaration(element, applyStylesToChildNodes, null, null); - } - - public void applyDefaultStyleDeclaration(Object element, boolean applyStylesToChildNodes, - CSSStyleDeclaration newStyle, String pseudoE) { - // Initial styles must be computed or applied - Element elt = getElement(element); - if (elt != null) { - if (elt instanceof CSSStylableElement stylableElement) { - CSSStyleDeclaration oldDefaultStyleDeclaration = stylableElement.getDefaultStyleDeclaration(pseudoE); - CSSStyleDeclaration defaultStyleDeclaration = getDefaultStyleDeclaration( - element, newStyle, pseudoE); - if (oldDefaultStyleDeclaration != null) { - // Second apply styles, apply the initial style - // before apply the new style - try { - throwError = false; - applyStyleDeclaration(element, defaultStyleDeclaration, pseudoE); - } finally { - throwError = true; - } - } - } - if (applyStylesToChildNodes) { - /* - * Style all children recursive. - */ - NodeList nodes = elt.getChildNodes(); - if (nodes != null) { - processNodeList(nodes, this::applyDefaultStyleDeclaration, applyStylesToChildNodes); - onStylesAppliedToChildNodes(elt, nodes); - } - } - } - } - - /** - * Delegates the handle method. - * - * @param element - * may be a widget or a node or some object - */ - @Override - public ICSSPropertyHandler applyCSSProperty(Object element, String property, CSSValue value, String pseudo) - throws Exception { - if (currentCSSPropertiesApplied != null && currentCSSPropertiesApplied.containsKey(property)) { - // CSS Property was already applied, ignore it. - return null; - } - - element = getElement(element); // in case we're passed a node - if ("inherit".equals(value.getCssText())) { - // go to parent node - Element actualElement = (Element) element; - Node parentNode = actualElement.getParentNode(); - // get CSS property value - String parentValueString = retrieveCSSProperty(parentNode, property, pseudo); - // and convert it to a CSS value, overriding the "inherit" setting - // with the parent value - value = parsePropertyValue(parentValueString); - } - - for (ICSSPropertyHandlerProvider provider : propertyHandlerProviders) { - Collection handlers = provider.getCSSPropertyHandlers(element, property); - if (handlers == null) { - continue; - } - for (ICSSPropertyHandler handler : handlers) { - try { - boolean result = handler.applyCSSProperty(element, property, value, pseudo, this); - if (result) { - // Add CSS Property to flag that this CSS Property was - // applied. - if (currentCSSPropertiesApplied != null) { - currentCSSPropertiesApplied.put(property, property); - } - return handler; - } - } catch (Exception e) { - if (throwError || (!throwError && !(e instanceof UnsupportedPropertyException))) { - handleExceptions(e); - } - } - } - } - - return null; - } - - @Override - public String retrieveCSSProperty(Object element, String property, String pseudo) { - try { - element = getElement(element); // in case we're passed a node - for (ICSSPropertyHandlerProvider provider : propertyHandlerProviders) { - Collection handlers = provider.getCSSPropertyHandlers(element, property); - if (handlers == null) { - continue; - } - for (ICSSPropertyHandler handler : handlers) { - String value = handler.retrieveCSSProperty(element, property, pseudo, this); - if (!StringUtils.isEmpty(value)) { - return value; - } - } - } - } catch (Exception e) { - handleExceptions(e); - } - return null; - } - - @Override - public String[] getCSSCompositePropertiesNames(String property) { - try { - Collection handlers = getCSSPropertyHandlers(property); - if (handlers == null) { - return null; - } - for (ICSSPropertyHandler handler : handlers) { - if (handler instanceof ICSSPropertyCompositeHandler compositeHandler) { - if (compositeHandler.isCSSPropertyComposite(property)) { - return compositeHandler.getCSSPropertiesNames(property); - } - } - } - } catch (Exception e) { - handleExceptions(e); - } - return null; - } - - protected Collection getCSSPropertyHandlers(String property) throws Exception { - Collection handlers = new ArrayList<>(); - for (ICSSPropertyHandlerProvider provider : propertyHandlerProviders) { - Collection h = provider.getCSSPropertyHandlers(property); - if (handlers == null) { - handlers = h; - } else { - handlers = new ArrayList<>(handlers); - handlers.addAll(h); - } - } - return handlers; - } - - /** - * Return the set of property names and handlers for the provided node. - * - * @return the property names and handlers - */ - @Override - public Collection getCSSProperties(Object element) { - Set properties = new HashSet<>(); - for (ICSSPropertyHandlerProvider provider : propertyHandlerProviders) { - properties.addAll(provider.getCSSProperties(element)); - } - return properties; - } - - /*--------------- Dynamic pseudo classes -----------------*/ - - @Override - public IElementProvider getElementProvider() { - return elementProvider; - } - - @Override - public void setElementProvider(IElementProvider elementProvider) { - this.elementProvider = elementProvider; - } - - /** - * Return the w3c Element linked to the Object element. - */ - @Override - public Element getElement(Object element) { - Element elt = null; - if (element == null) { - return elt; - } - CSSElementContext elementContext = getCSSElementContext(element); - if (elementContext != null) { - if (!elementContext.elementMustBeRefreshed(elementProvider)) { - return elementContext.getElement(); - } - } - if (element instanceof Element) { - elt = (Element) element; - } else if (elementProvider != null) { - elt = elementProvider.getElement(element, this); - } - if (elt != null) { - if (elementContext == null) { - elementContext = new CSSElementContextImpl(); - Object nativeWidget = getNativeWidget(element); - hookNativeWidget(nativeWidget); - getElementsContext().put(nativeWidget, elementContext); - } - elementContext.setElementProvider(elementProvider); - elementContext.setElement(elt); - if (elt instanceof CSSStylableElement) { - // Initialize CSS stylable element - ((CSSStylableElement)elt).initialize(); - } - - } - return elt; - } - - /** - * Called when an element context is created for a native widget and - * registered with this engine. Subclasses should override and install - * a listener on the widget that will call {@link #handleWidgetDisposed(Object)} - * when the widget is disposed. - *

- * The default implementation of this method does nothing. - *

- * - * @param widget the native widget to hook - */ - protected void hookNativeWidget(Object widget) { - } - - /** - * Called when a widget is disposed. Removes the element context - * from the element contexts map and the widgets map. Overriding - * classes must call the super implementation. - */ - @Override - public void handleWidgetDisposed(Object widget) { - if (elementsContext != null) { - elementsContext.remove(widget); - } - } - - @Override - public CSSElementContext getCSSElementContext(Object element) { - Object o = getNativeWidget(element); - return getElementsContext().get(o); - } - - public Object getNativeWidget(Object element) { - Object o = element; - if (element instanceof CSSStylableElement) { - o = ((CSSStylableElement) o).getNativeWidget(); - } - return o; - } - - protected Map getElementsContext() { - if (elementsContext == null) { - elementsContext = new HashMap<>(); - } - return elementsContext; - } - - @Override - public boolean matches(Selector selector, Object element, String pseudoElt) { - Element elt = getElement(element); - if (elt == null) { - return false; - } - if (selector instanceof ExtendedSelector extendedSelector) { - return extendedSelector.match(elt, pseudoElt); - } else { - // TODO : selector is not batik ExtendedSelector, - // Manage this case... - } - return false; - } - - /*--------------- Error Handler -----------------*/ - - /** - * Handle exceptions thrown while parsing, applying styles. By default this - * method call CSS Error Handler if it is initialized. - */ - @Override - public void handleExceptions(Exception e) { - if (errorHandler != null) { - errorHandler.error(e); - } - } - - @Override - public CSSErrorHandler getErrorHandler() { - return errorHandler; - } - - /** - * Set the CSS Error Handler to manage exception. - */ - @Override - public void setErrorHandler(CSSErrorHandler errorHandler) { - this.errorHandler = errorHandler; - } - - /*--------------- Resources Locator Manager -----------------*/ - - @Override - public IResourcesLocatorManager getResourcesLocatorManager() { - if (resourcesLocatorManager == null) { - return defaultResourcesLocatorManager; - } - return resourcesLocatorManager; - } - - @Override - public void setResourcesLocatorManager( - IResourcesLocatorManager resourcesLocatorManager) { - this.resourcesLocatorManager = resourcesLocatorManager; - } - - /*--------------- Document/View CSS -----------------*/ - - @Override - public DocumentCSS getDocumentCSS() { - return documentCSS; - } - - @Override - public ViewCSS getViewCSS() { - return viewCSS; - } - - @Override - public void dispose() { - reset(); - // Call dispose for each CSSStylableElement which was registered - Collection contexts = elementsContext.values(); - for (CSSElementContext context : contexts) { - Element element = context.getElement(); - if (element instanceof CSSStylableElement) { - ((CSSStylableElement) element).dispose(); - } - } - // FIXME: should dispose element provider and the property handler - // providers - elementsContext = null; - if (resourcesRegistry != null) { - resourcesRegistry.dispose(); - } - } - - @Override - public void reset() { - // Remove All Style Sheets - documentCSS.removeAllStyleSheets(); - } - - /*--------------- Resources Registry -----------------*/ - - @Override - public IResourcesRegistry getResourcesRegistry() { - return resourcesRegistry; - } - - @Override - public void setResourcesRegistry(IResourcesRegistry resourcesRegistry) { - this.resourcesRegistry = resourcesRegistry; - } - - public void registerCSSPropertyHandlerProvider(ICSSPropertyHandlerProvider handlerProvider) { - propertyHandlerProviders.add(handlerProvider); - } - - public void unregisterCSSPropertyHandlerProvider(ICSSPropertyHandlerProvider handlerProvider) { - propertyHandlerProviders.remove(handlerProvider); - } - - /*--------------- CSS Value Converter -----------------*/ - - @Override - public void registerCSSValueConverter(ICSSValueConverter converter) { - if (valueConverters == null) { - valueConverters = new HashMap<>(); - } - valueConverters.put(converter.getToType(), converter); - } - - @Override - public void unregisterCSSValueConverter(ICSSValueConverter converter) { - if (valueConverters == null) { - return; - } - valueConverters.remove(converter); - } - - @Override - public ICSSValueConverter getCSSValueConverter(Object toType) { - if (valueConverters != null) { - return valueConverters.get(toType); - } - return null; - } - - @Override - public Object convert(CSSValue value, Object toType, Object context) throws Exception { - if ("unset".equals(value.getCssText())) { - return null; - } - Object key = keyFactory.createKey(value); - Object newValue = getResource(toType, key); - - if (newValue == null) { - ICSSValueConverter converter = getCSSValueConverter(toType); - if (converter != null) { - newValue = converter.convert(value, this, context); - // cache it - registerResource(toType, key, newValue); - } - } - return newValue; - } - - private Object getResource(Object toType, Object key) { - if (key != null && getResourcesRegistry() != null) { - return getResourcesRegistry().getResource(toType, key); - } - return null; - } - - private void registerResource(Object toType, Object key, Object resource) { - if (key != null && resource != null && getResourcesRegistry() != null) { - getResourcesRegistry().registerResource(toType, key, resource); - } - } - - @Override - public String convert(Object value, Object toType, Object context) - throws Exception { - if (value == null) { - return null; - } - ICSSValueConverter converter = getCSSValueConverter(toType); - if (converter != null) { - return converter.convert(value, this, context); - } - return null; - } - - /*--------------- Abstract methods -----------------*/ - - /** - * Return instance of CSS Parser. - */ - public abstract CSSParser makeCSSParser(); - - protected void setResourceRegistryKeyFactory(ResourceRegistryKeyFactory keyFactory) { - this.keyFactory = keyFactory; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/CSSEngineImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/CSSEngineImpl.java index a463e82452d..24543d008e1 100644 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/CSSEngineImpl.java +++ b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/CSSEngineImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2015 Angelo Zerr and others. + * Copyright (c) 2008, 2020 Angelo Zerr and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -10,56 +10,1120 @@ * * Contributors: * Angelo Zerr - initial API and implementation - * Brian de Alwis (MTI) - move out registry-specific element provisioning + * IBM Corporation - ongoing development + * Red Hat Inc. (mistria) - Fixes suggested by FindBugs + * Red Hat Inc. (mistria) - Bug 413348: fix stream leak + * Lars Vogel - Bug 428715 + * Brian de Alwis (MTI) - Performance tweaks (Bug 430829) + * Dirk Fauth - Bug 479896 + * Patrik Suzzi - Bug 500402 + * Daniel Raap - Bug 511836 *******************************************************************************/ package org.eclipse.e4.ui.css.core.impl.engine; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.core.runtime.IPath; +import org.eclipse.e4.ui.css.core.dom.CSSStylableElement; +import org.eclipse.e4.ui.css.core.dom.ChildVisibilityAwareElement; +import org.eclipse.e4.ui.css.core.dom.ExtendedCSSRule; import org.eclipse.e4.ui.css.core.dom.ExtendedDocumentCSS; +import org.eclipse.e4.ui.css.core.dom.IElementProvider; +import org.eclipse.e4.ui.css.core.dom.IStreamingNodeList; import org.eclipse.e4.ui.css.core.dom.parsers.CSSParser; import org.eclipse.e4.ui.css.core.dom.parsers.CSSParserFactory; import org.eclipse.e4.ui.css.core.dom.parsers.ICSSParserFactory; +import org.eclipse.e4.ui.css.core.dom.properties.ICSSPropertyCompositeHandler; import org.eclipse.e4.ui.css.core.dom.properties.ICSSPropertyHandler; +import org.eclipse.e4.ui.css.core.dom.properties.ICSSPropertyHandlerProvider; import org.eclipse.e4.ui.css.core.dom.properties.converters.CSSValueBooleanConverterImpl; +import org.eclipse.e4.ui.css.core.dom.properties.converters.ICSSValueConverter; import org.eclipse.e4.ui.css.core.dom.properties.providers.CSSPropertyHandlerLazyProviderImpl; import org.eclipse.e4.ui.css.core.dom.properties.providers.CSSPropertyHandlerSimpleProviderImpl; -import org.eclipse.e4.ui.css.core.impl.sac.CSSConditionFactoryImpl; -import org.eclipse.e4.ui.css.core.impl.sac.CSSSelectorFactoryImpl; -import org.w3c.css.sac.ConditionFactory; +import org.eclipse.e4.ui.css.core.engine.CSSElementContext; +import org.eclipse.e4.ui.css.core.engine.CSSEngine; +import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler; +import org.eclipse.e4.ui.css.core.exceptions.UnsupportedPropertyException; +import org.eclipse.e4.ui.css.core.impl.dom.CSSRuleListImpl; +import org.eclipse.e4.ui.css.core.impl.dom.CSSStyleSheetImpl; +import org.eclipse.e4.ui.css.core.impl.dom.DocumentCSSImpl; +import org.eclipse.e4.ui.css.core.impl.dom.ViewCSSImpl; +import org.eclipse.e4.ui.css.core.impl.engine.selector.SacTranslator; +import org.eclipse.e4.ui.css.core.impl.engine.selector.SelectorMatcher; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors; +import org.eclipse.e4.ui.css.core.resources.IResourcesRegistry; +import org.eclipse.e4.ui.css.core.resources.ResourceRegistryKeyFactory; +import org.eclipse.e4.ui.css.core.util.impl.resources.ResourcesLocatorManager; +import org.eclipse.e4.ui.css.core.util.resources.IResourcesLocatorManager; +import org.eclipse.e4.ui.css.core.utils.StringUtils; +import org.apache.batik.css.parser.DefaultConditionFactory; +import org.apache.batik.css.parser.DefaultSelectorFactory; +import org.w3c.css.sac.InputSource; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.css.CSSImportRule; +import org.w3c.dom.css.CSSRule; +import org.w3c.dom.css.CSSRuleList; +import org.w3c.dom.css.CSSStyleDeclaration; +import org.w3c.dom.css.CSSStyleSheet; +import org.w3c.dom.css.CSSValue; +import org.w3c.dom.css.DocumentCSS; +import org.w3c.dom.css.ViewCSS; +import org.w3c.dom.stylesheets.StyleSheet; -public abstract class CSSEngineImpl extends AbstractCSSEngine { +/** + * Abstract CSS Engine manage style sheet parsing and store the + * {@link CSSStyleSheet} into {@link DocumentCSS}. + * + * To apply styles, call the {@link #applyStyles(Object, boolean, boolean)} + * method. This method check if {@link ICSSPropertyHandler} is registered for + * apply the CSS property. + * + * @version 1.0.0 + * @author Angelo ZERR + */ +public abstract class CSSEngineImpl implements CSSEngine { + + /** + * Archives are deliberately identified by exclamation mark in URLs + */ + private static final String ARCHIVE_IDENTIFIER = "!"; + + /** + * Default {@link IResourcesLocatorManager} used to get InputStream, Reader + * resource like Image. + */ + private static final IResourcesLocatorManager defaultResourcesLocatorManager = ResourcesLocatorManager.INSTANCE; + + /** + * w3c {@link DocumentCSS}. + */ + private final ExtendedDocumentCSS documentCSS; + + /** + * w3c {@link ViewCSS}. + */ + private final ViewCSS viewCSS; + + /** + * {@link IElementProvider} used to retrieve w3c Element linked to the + * widget. + */ + private IElementProvider elementProvider; + + protected boolean computeDefaultStyle = false; + + private Map elementsContext = null; + + /** + * CSS Error Handler to intercept error while parsing, applying styles. + */ + private CSSErrorHandler errorHandler; - public static final ConditionFactory CONDITIONFACTORY_INSTANCE = new CSSConditionFactoryImpl( - null, "class", null, "id"); + private IResourcesLocatorManager resourcesLocatorManager; + + private IResourcesRegistry resourcesRegistry; + + /** + * An ordered list of ICSSPropertyHandlerProvider + */ + protected List propertyHandlerProviders = new ArrayList<>(); + // for performance hold a map of handlers to singleton list + private final Map> propertyHandlerInstanceMap = new HashMap<>(); + + private Map currentCSSPropertiesApplied; + + private boolean throwError; + + private Map valueConverters = null; + + private int parseImport; + + private ResourceRegistryKeyFactory keyFactory; private CSSPropertyHandlerSimpleProviderImpl handlerProvider; private CSSPropertyHandlerLazyProviderImpl lazyHandlerProvider; public CSSEngineImpl() { - super(); - - // Register SWT Boolean CSSValue Converter - super.registerCSSValueConverter(CSSValueBooleanConverterImpl.INSTANCE); + this(new DocumentCSSImpl()); } public CSSEngineImpl(ExtendedDocumentCSS documentCSS) { - super(documentCSS); - // Register SWT Boolean CSSValue Converter - super.registerCSSValueConverter(CSSValueBooleanConverterImpl.INSTANCE); + this.documentCSS = documentCSS; + this.viewCSS = new ViewCSSImpl(documentCSS); + keyFactory = new ResourceRegistryKeyFactory(); + registerCSSValueConverter(CSSValueBooleanConverterImpl.INSTANCE); + } + + /*--------------- Parse style sheet -----------------*/ + + @Override + public StyleSheet parseStyleSheet(Reader reader) throws IOException { + InputSource source = new InputSource(); + source.setCharacterStream(reader); + return parseStyleSheet(source); } @Override - public CSSParser makeCSSParser() { - // Create CSS Parser - ICSSParserFactory factory = CSSParserFactory.newInstance(); - CSSParser parser = factory.makeCSSParser(); + public StyleSheet parseStyleSheet(InputStream stream) throws IOException { + InputSource source = new InputSource(); + source.setByteStream(stream); + return parseStyleSheet(source); + } - // Register Batik CSS Selector factory. - parser.setSelectorFactory(CSSSelectorFactoryImpl.INSTANCE); + @Override + public StyleSheet parseStyleSheet(InputSource source) throws IOException { + // Check that CharacterStream or ByteStream is not null + checkInputSource(source); + CSSParser parser = makeCSSParser(); + CSSStyleSheet styleSheet = parser.parseStyleSheet(source); - // Register Custom CSS Condition factory. - parser.setConditionFactory(CONDITIONFACTORY_INSTANCE); + CSSRuleList rules = styleSheet.getCssRules(); + int length = rules.getLength(); + CSSRuleListImpl masterList = new CSSRuleListImpl(); + int counter; + for (counter = 0; counter < length; counter++) { + CSSRule rule = rules.item(counter); + if (rule.getType() != CSSRule.IMPORT_RULE) { + break; + } + // processing an import CSS + CSSImportRule importRule = (CSSImportRule) rule; + URL url = null; + if (importRule.getHref().startsWith("platform")) { + url = FileLocator.resolve(new URL(importRule.getHref())); + } else { + IPath p = IPath.fromOSString(source.getURI()); + IPath trim = p.removeLastSegments(1); + boolean isArchive = source.getURI().contains(ARCHIVE_IDENTIFIER); + url = FileLocator + .resolve(new URL(trim.addTrailingSeparator().toString() + ((CSSImportRule) rule).getHref())); + File testFile = new File(url.getFile()); + if (!isArchive&&!testFile.exists()) { + // look in platform default + String path = getResourcesLocatorManager().resolve((importRule).getHref()); + testFile = new File(new URL(path).getFile()); + if (testFile.exists()) { + url = new URL(path); + } + } + } + try (InputStream stream = url.openStream()) { + InputSource tempStream = new InputSource(); + tempStream.setURI(url.toString()); + tempStream.setByteStream(stream); + parseImport++; + try { + styleSheet = (CSSStyleSheet) this.parseStyleSheet(tempStream); + } finally { + parseImport--; + } + CSSRuleList tempRules = styleSheet.getCssRules(); + for (int j = 0; j < tempRules.getLength(); j++) { + masterList.add(tempRules.item(j)); + } + } + } + // add remaining non import rules + for (int i = counter; i < length; i++) { + masterList.add(rules.item(i)); + } + + // final stylesheet + CSSStyleSheetImpl s = new CSSStyleSheetImpl(); + s.setRuleList(masterList); + if (parseImport == 0) { + documentCSS.addStyleSheet(s); + } + return s; + } + + private void processNodeList(NodeList nodes, BiConsumer consumer, boolean applyStylesToChildNodes) { + if (nodes instanceof IStreamingNodeList) { + ((IStreamingNodeList) nodes).stream().forEach(child -> { + consumer.accept(child, applyStylesToChildNodes); + }); + } else { + int length = nodes.getLength(); + for (int k = 0; k < length; k++) { + consumer.accept(nodes.item(k), applyStylesToChildNodes); + } + } + } + + /** + * Return true if source is valid and false otherwise. + */ + private void checkInputSource(InputSource source) throws IOException { + Reader reader = source.getCharacterStream(); + InputStream stream = source.getByteStream(); + if (reader == null && stream == null) { + throw new IOException( + "CharacterStream or ByteStream cannot be null for the InputSource."); + } + } + + /*--------------- Parse style declaration -----------------*/ + + @Override + public CSSStyleDeclaration parseStyleDeclaration(String style) { + try { + return parseStyleDeclaration(new StringReader(style)); + } catch (IOException e) { + throw new IllegalStateException("StringReader cannot throw IOException", e); //$NON-NLS-1$ + } + } + + @Override + public CSSStyleDeclaration parseStyleDeclaration(Reader reader) throws IOException { + InputSource source = new InputSource(); + source.setCharacterStream(reader); + return parseStyleDeclaration(source); + } + + @Override + public CSSStyleDeclaration parseStyleDeclaration(InputStream stream) throws IOException { + InputSource source = new InputSource(); + source.setByteStream(stream); + return parseStyleDeclaration(source); + } + + @Override + public CSSStyleDeclaration parseStyleDeclaration(InputSource source) throws IOException { + checkInputSource(source); + CSSParser parser = makeCSSParser(); + return parser.parseStyleDeclaration(source); + } + + /*--------------- Parse CSS Selector -----------------*/ + + @Override + public Selectors.SelectorList parseSelectors(String selector) { + try { + return parseSelectors(new StringReader(selector)); + } catch (IOException e) { + throw new IllegalStateException("StringReader cannot throw IOException", e); //$NON-NLS-1$ + } + } + + @Override + public Selectors.SelectorList parseSelectors(Reader reader) throws IOException { + InputSource source = new InputSource(); + source.setCharacterStream(reader); + return parseSelectors(source); + } + + @Override + public Selectors.SelectorList parseSelectors(InputStream stream) throws IOException { + InputSource source = new InputSource(); + source.setByteStream(stream); + return parseSelectors(source); + } + + @Override + public Selectors.SelectorList parseSelectors(InputSource source) throws IOException { + checkInputSource(source); + CSSParser parser = makeCSSParser(); + return SacTranslator.translate(parser.parseSelectors(source)); + } + + /*--------------- Parse CSS Property Value-----------------*/ + + @Override + public CSSValue parsePropertyValue(Reader reader) throws IOException { + InputSource source = new InputSource(); + source.setCharacterStream(reader); + return parsePropertyValue(source); + } + + @Override + public CSSValue parsePropertyValue(InputStream stream) throws IOException { + InputSource source = new InputSource(); + source.setByteStream(stream); + return parsePropertyValue(source); + } + + @Override + public CSSValue parsePropertyValue(String value) { + try { + return parsePropertyValue(new StringReader(value)); + } catch (IOException e) { + throw new IllegalStateException("StringReader cannot throw IOException", e); //$NON-NLS-1$ + } + } + + @Override + public CSSValue parsePropertyValue(InputSource source) throws IOException { + checkInputSource(source); + CSSParser parser = makeCSSParser(); + return parser.parsePropertyValue(source); + } + + /*--------------- Apply styles -----------------*/ + + @Override + public void applyStyles(Object element, boolean applyStylesToChildNodes) { + applyStyles(element, applyStylesToChildNodes, computeDefaultStyle); + } + + @Override + public void applyStyles(Object element, boolean applyStylesToChildNodes, boolean computeDefaultStyle) { + Element elt = getElement(element); + if (elt == null || !isVisible(elt)) { + return; + } + + /* + * Compute new Style to apply. + */ + CSSStyleDeclaration style = viewCSS.getComputedStyle(elt, null); + if (computeDefaultStyle) { + if (applyStylesToChildNodes) { + this.computeDefaultStyle = computeDefaultStyle; + } + /* + * Apply default style. + */ + applyDefaultStyleDeclaration(element, false, style, null); + } + + /* + * Manage static pseudo instances + */ + String[] pseudoInstances = getStaticPseudoInstances(elt); + if (pseudoInstances != null && pseudoInstances.length > 0) { + // there are static pseudo instances defined, loop for it and + // apply styles for each pseudo instance. + for (String pseudoInstance : pseudoInstances) { + CSSStyleDeclaration styleWithPseudoInstance = viewCSS.getComputedStyle(elt, pseudoInstance); + if (computeDefaultStyle) { + /* + * Apply default style for the current pseudo instance. + */ + applyDefaultStyleDeclaration(element, false, styleWithPseudoInstance, pseudoInstance); + } + + if (styleWithPseudoInstance != null) { + CSSRule parentRule = styleWithPseudoInstance.getParentRule(); + if (parentRule instanceof ExtendedCSSRule) { + applyConditionalPseudoStyle((ExtendedCSSRule) parentRule, pseudoInstance, element, styleWithPseudoInstance); + } else { + applyStyleDeclaration(elt, styleWithPseudoInstance, pseudoInstance); + } + } + } + } + + if (style != null) { + applyStyleDeclaration(elt, style, null); + } + try { + // Apply inline style + applyInlineStyle(elt, false); + } catch (Exception e) { + handleExceptions(e); + } + + if (applyStylesToChildNodes) { + /* + * Style all children recursive. + */ + NodeList nodes = elt instanceof ChildVisibilityAwareElement c + ? c.getVisibleChildNodes() + : elt.getChildNodes(); + if (nodes != null) { + processNodeList(nodes, this::applyStyles, applyStylesToChildNodes); + onStylesAppliedToChildNodes(elt, nodes); + } + } + } + + /** + * Allow the CSS engine to skip particular elements if they are not visible. + * Elements need to be restyled when they become visible. + * + * @return true if the element is visible, false if not visible. + */ + protected boolean isVisible(Element elt) { + Node parentNode = elt.getParentNode(); + if (parentNode instanceof ChildVisibilityAwareElement) { + NodeList l = ((ChildVisibilityAwareElement) parentNode).getVisibleChildNodes(); + if (l != null) { + if (l instanceof IStreamingNodeList) { + return ((IStreamingNodeList) l).stream().anyMatch(node -> node == elt); + } else { + int length = l.getLength(); + for (int i = 0; i < length; i++) { + if (l.item(i) == elt) { + return true; + } + } + } + } + return false; + } + return true; + } + + private void applyConditionalPseudoStyle(ExtendedCSSRule parentRule, String pseudoInstance, Object element, + CSSStyleDeclaration styleWithPseudoInstance) { + Selectors.SelectorList selectorList = parentRule.getSelectorList(); + for (Selectors.Selector alternative : selectorList.alternatives()) { + if (matchesPseudoInstanceAttribute(alternative, pseudoInstance)) { + applyStyleDeclaration(element, styleWithPseudoInstance, pseudoInstance); + return; + } + } + } + + /** + * Returns {@code true} if {@code selector} carries a pseudo-class or + * attribute selector (anywhere in a compound or descendant combinator) + * whose target value equals {@code pseudoInstance}. Mirrors the legacy + * SAC walker, which handled both {@code :selected} (a pseudo-class) and + * {@code Shell[active='true']} (an attribute) through SAC's shared + * {@code AttributeCondition} interface. + */ + private static boolean matchesPseudoInstanceAttribute(Selectors.Selector selector, String pseudoInstance) { + if (selector instanceof Selectors.PseudoClass pc) { + return pseudoInstance.equals(pc.name()); + } + if (selector instanceof Selectors.AttributeSelector attr) { + return pseudoInstance.equals(attr.value()); + } + if (selector instanceof Selectors.AttributeIncludes attr) { + return pseudoInstance.equals(attr.value()); + } + if (selector instanceof Selectors.AttributeBeginHyphen attr) { + return pseudoInstance.equals(attr.value()); + } + if (selector instanceof Selectors.And and) { + return matchesPseudoInstanceAttribute(and.left(), pseudoInstance) + || matchesPseudoInstanceAttribute(and.right(), pseudoInstance); + } + if (selector instanceof Selectors.Descendant d) { + return matchesPseudoInstanceAttribute(d.descendant(), pseudoInstance) + || matchesPseudoInstanceAttribute(d.ancestor(), pseudoInstance); + } + if (selector instanceof Selectors.Child c) { + return matchesPseudoInstanceAttribute(c.child(), pseudoInstance) + || matchesPseudoInstanceAttribute(c.parent(), pseudoInstance); + } + if (selector instanceof Selectors.Adjacent a) { + return matchesPseudoInstanceAttribute(a.second(), pseudoInstance) + || matchesPseudoInstanceAttribute(a.first(), pseudoInstance); + } + return false; + } + + protected String[] getStaticPseudoInstances(Element element) { + if (element instanceof CSSStylableElement stylableElement) { + return stylableElement.getStaticPseudoInstances(); + } + return null; + } + + /** + * Callback method called when styles applied of nodes + * children of the element. + */ + protected void onStylesAppliedToChildNodes(Element element, NodeList nodes) { + if (element instanceof CSSStylableElement) { + ((CSSStylableElement) element).onStylesApplied(nodes); + } + } + + /*--------------- Apply style declaration -----------------*/ + + @Override + public void applyStyleDeclaration(Object element, CSSStyleDeclaration style, String pseudo) { + // Apply style + boolean avoidanceCacheInstalled = currentCSSPropertiesApplied == null; + if (avoidanceCacheInstalled) { + currentCSSPropertiesApplied = new HashMap<>(); + } + List appliedHandlers = Collections.emptyList(); + for (int i = 0; i < style.getLength(); i++) { + String property = style.item(i); + CSSValue value = style.getPropertyCSSValue(property); + try { + ICSSPropertyHandler handler = this.applyCSSProperty(element, property, value, pseudo); + if (handler != null) { + switch (appliedHandlers.size()) { + case 0: + appliedHandlers = propertyHandlerInstanceMap.computeIfAbsent(handler, + Collections::singletonList); + break; + case 1: + appliedHandlers = new ArrayList<>(appliedHandlers); + appliedHandlers.add(handler); + break; + default: + if (!appliedHandlers.contains(handler)) { + appliedHandlers.add(handler); + } + } + } + } catch (Exception e) { + if (throwError || (!throwError && !(e instanceof UnsupportedPropertyException))) { + handleExceptions(e); + } + } + } + for (ICSSPropertyHandler handler : appliedHandlers) { + try { + handler.onAllCSSPropertiesApplied(element, this, pseudo); + } catch (Exception e) { + handleExceptions(e); + } + } + if (avoidanceCacheInstalled) { + currentCSSPropertiesApplied = null; + } + + } + + @Override + public CSSStyleDeclaration parseAndApplyStyleDeclaration(Object node, + Reader reader) throws IOException { + CSSStyleDeclaration style = parseStyleDeclaration(reader); + this.applyStyleDeclaration(node, style, null); + return style; + } + + @Override + public CSSStyleDeclaration parseAndApplyStyleDeclaration(Object node, InputStream stream) throws IOException { + CSSStyleDeclaration style = parseStyleDeclaration(stream); + this.applyStyleDeclaration(node, style, null); + return style; + } + + @Override + public CSSStyleDeclaration parseAndApplyStyleDeclaration(Object node, InputSource source) throws IOException { + CSSStyleDeclaration style = parseStyleDeclaration(source); + this.applyStyleDeclaration(node, style, null); + return style; + } + + @Override + public CSSStyleDeclaration parseAndApplyStyleDeclaration(Object node, String style) throws IOException { + CSSStyleDeclaration styleDeclaration = parseStyleDeclaration(style); + this.applyStyleDeclaration(node, styleDeclaration, null); + return styleDeclaration; + } + + /*--------------- Apply inline style -----------------*/ + + @Override + public void applyInlineStyle(Object node, boolean applyStylesToChildNodes) { + Element elt = getElement(node); + if (elt != null) { + if (elt instanceof CSSStylableElement stylableElement) { + String style = stylableElement.getCSSStyle(); + if (style != null && style.length() > 0) { + try { + parseAndApplyStyleDeclaration(stylableElement.getNativeWidget(), style); + } catch (IOException e) { + handleExceptions(e); + } + } + } + if (applyStylesToChildNodes) { + /* + * Style all children recursive. + */ + NodeList nodes = elt.getChildNodes(); + if (nodes != null) { + processNodeList(nodes, this::applyInlineStyle, applyStylesToChildNodes); + } + } + } + } + + /*--------------- Initial Style -----------------*/ + + @Override + public CSSStyleDeclaration getDefaultStyleDeclaration(Object element, String pseudoE) { + return getDefaultStyleDeclaration(element, null, pseudoE); + } + + public CSSStyleDeclaration getDefaultStyleDeclaration(Object widget, CSSStyleDeclaration newStyle, String pseudoE) { + CSSStyleDeclaration style = null; + for (ICSSPropertyHandlerProvider provider : propertyHandlerProviders) { + try { + style = provider.getDefaultCSSStyleDeclaration(this, widget, newStyle, pseudoE); + } catch (Exception e) { + handleExceptions(e); + } + } + return style; + } + + @Override + public void applyDefaultStyleDeclaration(Object element, boolean applyStylesToChildNodes) { + applyDefaultStyleDeclaration(element, applyStylesToChildNodes, null, null); + } + + public void applyDefaultStyleDeclaration(Object element, boolean applyStylesToChildNodes, + CSSStyleDeclaration newStyle, String pseudoE) { + // Initial styles must be computed or applied + Element elt = getElement(element); + if (elt != null) { + if (elt instanceof CSSStylableElement stylableElement) { + CSSStyleDeclaration oldDefaultStyleDeclaration = stylableElement.getDefaultStyleDeclaration(pseudoE); + CSSStyleDeclaration defaultStyleDeclaration = getDefaultStyleDeclaration( + element, newStyle, pseudoE); + if (oldDefaultStyleDeclaration != null) { + // Second apply styles, apply the initial style + // before apply the new style + try { + throwError = false; + applyStyleDeclaration(element, defaultStyleDeclaration, pseudoE); + } finally { + throwError = true; + } + } + } + if (applyStylesToChildNodes) { + /* + * Style all children recursive. + */ + NodeList nodes = elt.getChildNodes(); + if (nodes != null) { + processNodeList(nodes, this::applyDefaultStyleDeclaration, applyStylesToChildNodes); + onStylesAppliedToChildNodes(elt, nodes); + } + } + } + } + + /** + * Delegates the handle method. + * + * @param element + * may be a widget or a node or some object + */ + @Override + public ICSSPropertyHandler applyCSSProperty(Object element, String property, CSSValue value, String pseudo) + throws Exception { + if (currentCSSPropertiesApplied != null && currentCSSPropertiesApplied.containsKey(property)) { + // CSS Property was already applied, ignore it. + return null; + } + + element = getElement(element); // in case we're passed a node + if ("inherit".equals(value.getCssText())) { + // go to parent node + Element actualElement = (Element) element; + Node parentNode = actualElement.getParentNode(); + // get CSS property value + String parentValueString = retrieveCSSProperty(parentNode, property, pseudo); + // and convert it to a CSS value, overriding the "inherit" setting + // with the parent value + value = parsePropertyValue(parentValueString); + } + + for (ICSSPropertyHandlerProvider provider : propertyHandlerProviders) { + Collection handlers = provider.getCSSPropertyHandlers(element, property); + if (handlers == null) { + continue; + } + for (ICSSPropertyHandler handler : handlers) { + try { + boolean result = handler.applyCSSProperty(element, property, value, pseudo, this); + if (result) { + // Add CSS Property to flag that this CSS Property was + // applied. + if (currentCSSPropertiesApplied != null) { + currentCSSPropertiesApplied.put(property, property); + } + return handler; + } + } catch (Exception e) { + if (throwError || (!throwError && !(e instanceof UnsupportedPropertyException))) { + handleExceptions(e); + } + } + } + } + + return null; + } + + @Override + public String retrieveCSSProperty(Object element, String property, String pseudo) { + try { + element = getElement(element); // in case we're passed a node + for (ICSSPropertyHandlerProvider provider : propertyHandlerProviders) { + Collection handlers = provider.getCSSPropertyHandlers(element, property); + if (handlers == null) { + continue; + } + for (ICSSPropertyHandler handler : handlers) { + String value = handler.retrieveCSSProperty(element, property, pseudo, this); + if (!StringUtils.isEmpty(value)) { + return value; + } + } + } + } catch (Exception e) { + handleExceptions(e); + } + return null; + } + + @Override + public String[] getCSSCompositePropertiesNames(String property) { + try { + Collection handlers = getCSSPropertyHandlers(property); + if (handlers == null) { + return null; + } + for (ICSSPropertyHandler handler : handlers) { + if (handler instanceof ICSSPropertyCompositeHandler compositeHandler) { + if (compositeHandler.isCSSPropertyComposite(property)) { + return compositeHandler.getCSSPropertiesNames(property); + } + } + } + } catch (Exception e) { + handleExceptions(e); + } + return null; + } + + protected Collection getCSSPropertyHandlers(String property) throws Exception { + Collection handlers = new ArrayList<>(); + for (ICSSPropertyHandlerProvider provider : propertyHandlerProviders) { + Collection h = provider.getCSSPropertyHandlers(property); + if (handlers == null) { + handlers = h; + } else { + handlers = new ArrayList<>(handlers); + handlers.addAll(h); + } + } + return handlers; + } + + /** + * Return the set of property names and handlers for the provided node. + * + * @return the property names and handlers + */ + @Override + public Collection getCSSProperties(Object element) { + Set properties = new HashSet<>(); + for (ICSSPropertyHandlerProvider provider : propertyHandlerProviders) { + properties.addAll(provider.getCSSProperties(element)); + } + return properties; + } + + /*--------------- Dynamic pseudo classes -----------------*/ + + @Override + public IElementProvider getElementProvider() { + return elementProvider; + } + + @Override + public void setElementProvider(IElementProvider elementProvider) { + this.elementProvider = elementProvider; + } + + /** + * Return the w3c Element linked to the Object element. + */ + @Override + public Element getElement(Object element) { + Element elt = null; + if (element == null) { + return elt; + } + CSSElementContext elementContext = getCSSElementContext(element); + if (elementContext != null) { + if (!elementContext.elementMustBeRefreshed(elementProvider)) { + return elementContext.getElement(); + } + } + if (element instanceof Element) { + elt = (Element) element; + } else if (elementProvider != null) { + elt = elementProvider.getElement(element, this); + } + if (elt != null) { + if (elementContext == null) { + elementContext = new CSSElementContextImpl(); + Object nativeWidget = getNativeWidget(element); + hookNativeWidget(nativeWidget); + getElementsContext().put(nativeWidget, elementContext); + } + elementContext.setElementProvider(elementProvider); + elementContext.setElement(elt); + if (elt instanceof CSSStylableElement) { + // Initialize CSS stylable element + ((CSSStylableElement)elt).initialize(); + } + + } + return elt; + } + + /** + * Called when an element context is created for a native widget and + * registered with this engine. Subclasses should override and install + * a listener on the widget that will call {@link #handleWidgetDisposed(Object)} + * when the widget is disposed. + *

+ * The default implementation of this method does nothing. + *

+ * + * @param widget the native widget to hook + */ + protected void hookNativeWidget(Object widget) { + } + + /** + * Called when a widget is disposed. Removes the element context + * from the element contexts map and the widgets map. Overriding + * classes must call the super implementation. + */ + @Override + public void handleWidgetDisposed(Object widget) { + if (elementsContext != null) { + elementsContext.remove(widget); + } + } + + @Override + public CSSElementContext getCSSElementContext(Object element) { + Object o = getNativeWidget(element); + return getElementsContext().get(o); + } + + public Object getNativeWidget(Object element) { + Object o = element; + if (element instanceof CSSStylableElement) { + o = ((CSSStylableElement) o).getNativeWidget(); + } + return o; + } + + protected Map getElementsContext() { + if (elementsContext == null) { + elementsContext = new HashMap<>(); + } + return elementsContext; + } + + @Override + public boolean matches(Selectors.Selector selector, Object element, String pseudoElt) { + Element elt = getElement(element); + if (elt == null) { + return false; + } + return SelectorMatcher.matches(selector, elt, pseudoElt); + } + + /*--------------- Error Handler -----------------*/ + + /** + * Handle exceptions thrown while parsing, applying styles. By default this + * method call CSS Error Handler if it is initialized. + */ + @Override + public void handleExceptions(Exception e) { + if (errorHandler != null) { + errorHandler.error(e); + } + } + + @Override + public CSSErrorHandler getErrorHandler() { + return errorHandler; + } + + /** + * Set the CSS Error Handler to manage exception. + */ + @Override + public void setErrorHandler(CSSErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + /*--------------- Resources Locator Manager -----------------*/ + + @Override + public IResourcesLocatorManager getResourcesLocatorManager() { + if (resourcesLocatorManager == null) { + return defaultResourcesLocatorManager; + } + return resourcesLocatorManager; + } + + @Override + public void setResourcesLocatorManager( + IResourcesLocatorManager resourcesLocatorManager) { + this.resourcesLocatorManager = resourcesLocatorManager; + } + + /*--------------- Document/View CSS -----------------*/ + + @Override + public DocumentCSS getDocumentCSS() { + return documentCSS; + } + + @Override + public ViewCSS getViewCSS() { + return viewCSS; + } + + @Override + public void dispose() { + reset(); + // Call dispose for each CSSStylableElement which was registered + Collection contexts = elementsContext.values(); + for (CSSElementContext context : contexts) { + Element element = context.getElement(); + if (element instanceof CSSStylableElement) { + ((CSSStylableElement) element).dispose(); + } + } + // FIXME: should dispose element provider and the property handler + // providers + elementsContext = null; + if (resourcesRegistry != null) { + resourcesRegistry.dispose(); + } + } + + @Override + public void reset() { + // Remove All Style Sheets + documentCSS.removeAllStyleSheets(); + } + + /*--------------- Resources Registry -----------------*/ + + @Override + public IResourcesRegistry getResourcesRegistry() { + return resourcesRegistry; + } + + @Override + public void setResourcesRegistry(IResourcesRegistry resourcesRegistry) { + this.resourcesRegistry = resourcesRegistry; + } + + public void registerCSSPropertyHandlerProvider(ICSSPropertyHandlerProvider handlerProvider) { + propertyHandlerProviders.add(handlerProvider); + } + + public void unregisterCSSPropertyHandlerProvider(ICSSPropertyHandlerProvider handlerProvider) { + propertyHandlerProviders.remove(handlerProvider); + } + + /*--------------- CSS Value Converter -----------------*/ + + @Override + public void registerCSSValueConverter(ICSSValueConverter converter) { + if (valueConverters == null) { + valueConverters = new HashMap<>(); + } + valueConverters.put(converter.getToType(), converter); + } + + @Override + public void unregisterCSSValueConverter(ICSSValueConverter converter) { + if (valueConverters == null) { + return; + } + valueConverters.remove(converter); + } + + @Override + public ICSSValueConverter getCSSValueConverter(Object toType) { + if (valueConverters != null) { + return valueConverters.get(toType); + } + return null; + } + + @Override + public Object convert(CSSValue value, Object toType, Object context) throws Exception { + if ("unset".equals(value.getCssText())) { + return null; + } + Object key = keyFactory.createKey(value); + Object newValue = getResource(toType, key); + + if (newValue == null) { + ICSSValueConverter converter = getCSSValueConverter(toType); + if (converter != null) { + newValue = converter.convert(value, this, context); + // cache it + registerResource(toType, key, newValue); + } + } + return newValue; + } + + private Object getResource(Object toType, Object key) { + if (key != null && getResourcesRegistry() != null) { + return getResourcesRegistry().getResource(toType, key); + } + return null; + } + + private void registerResource(Object toType, Object key, Object resource) { + if (key != null && resource != null && getResourcesRegistry() != null) { + getResourcesRegistry().registerResource(toType, key, resource); + } + } + + @Override + public String convert(Object value, Object toType, Object context) + throws Exception { + if (value == null) { + return null; + } + ICSSValueConverter converter = getCSSValueConverter(toType); + if (converter != null) { + return converter.convert(value, this, context); + } + return null; + } + + /** + * Return instance of CSS Parser, configured with Batik's stock selector + * and condition factories. Selectors flow through {@link SacTranslator} + * before they reach engine code, so we no longer need our vendored copies + * of the SAC factory classes. + */ + public CSSParser makeCSSParser() { + ICSSParserFactory factory = CSSParserFactory.newInstance(); + CSSParser parser = factory.makeCSSParser(); + parser.setSelectorFactory(DefaultSelectorFactory.INSTANCE); + parser.setConditionFactory(DefaultConditionFactory.INSTANCE); return parser; } @@ -71,7 +1135,7 @@ public void registerCSSPropertyHandler(Class cl, ICSSPropertyHandler handler) private void initHandlerProviderIfNeed() { if (handlerProvider == null) { handlerProvider = new CSSPropertyHandlerSimpleProviderImpl(); - super.registerCSSPropertyHandlerProvider(handlerProvider); + registerCSSPropertyHandlerProvider(handlerProvider); } } @@ -83,7 +1147,7 @@ public void registerCSSProperty(String propertyName, Class - initial API and implementation + *******************************************************************************/ +package org.eclipse.e4.ui.css.core.impl.engine.selector; + +import java.util.ArrayList; +import java.util.List; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Adjacent; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.And; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.AttributeBeginHyphen; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.AttributeIncludes; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.AttributeSelector; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Child; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.ClassSelector; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Descendant; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.ElementType; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.IdSelector; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.PseudoClass; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Selector; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Universal; +import org.w3c.css.sac.AttributeCondition; +import org.w3c.css.sac.CombinatorCondition; +import org.w3c.css.sac.Condition; +import org.w3c.css.sac.ConditionalSelector; +import org.w3c.css.sac.DescendantSelector; +import org.w3c.css.sac.ElementSelector; +import org.w3c.css.sac.SelectorList; +import org.w3c.css.sac.SiblingSelector; +import org.w3c.css.sac.SimpleSelector; + +/** + * Converts a SAC selector tree (as produced by the Batik parser) into the + * engine's internal {@link Selectors} AST. + * + *

+ * The translator is the single boundary between the SAC parser output and the + * rest of the engine. Once a stylesheet has been parsed, only the internal + * AST flows through {@code CSSEngine.matches}, the rule list, and + * {@link SelectorMatcher}. SAC types do not cross this boundary. + *

+ * + *

+ * Specificity is preserved exactly: the internal records compute it the same + * way the legacy SAC wrappers did (100 per id, 10 per class / attribute / + * pseudo-class, 1 per element, 0 for {@code *}). Combinators sum operands. + *

+ */ +public final class SacTranslator { + + private SacTranslator() { + // statics only + } + + /** Translate an entire {@link SelectorList} into the internal form. */ + public static Selectors.SelectorList translate(SelectorList sacList) { + List alternatives = new ArrayList<>(sacList.getLength()); + for (int i = 0; i < sacList.getLength(); i++) { + alternatives.add(translate(sacList.item(i))); + } + return new Selectors.SelectorList(alternatives); + } + + /** Translate a single SAC {@link org.w3c.css.sac.Selector}. */ + public static Selector translate(org.w3c.css.sac.Selector sac) { + return switch (sac.getSelectorType()) { + case org.w3c.css.sac.Selector.SAC_ELEMENT_NODE_SELECTOR -> translateElement((ElementSelector) sac); + case org.w3c.css.sac.Selector.SAC_PSEUDO_ELEMENT_SELECTOR -> translatePseudoElement((ElementSelector) sac); + case org.w3c.css.sac.Selector.SAC_CONDITIONAL_SELECTOR -> translateConditional((ConditionalSelector) sac); + case org.w3c.css.sac.Selector.SAC_DESCENDANT_SELECTOR -> { + DescendantSelector d = (DescendantSelector) sac; + yield new Descendant(translate(d.getAncestorSelector()), translateSimple(d.getSimpleSelector())); + } + case org.w3c.css.sac.Selector.SAC_CHILD_SELECTOR -> { + DescendantSelector c = (DescendantSelector) sac; + yield new Child(translate(c.getAncestorSelector()), translateSimple(c.getSimpleSelector())); + } + case org.w3c.css.sac.Selector.SAC_DIRECT_ADJACENT_SELECTOR -> { + SiblingSelector s = (SiblingSelector) sac; + yield new Adjacent(translate(s.getSelector()), translateSimple(s.getSiblingSelector())); + } + default -> throw new IllegalArgumentException( + "Unsupported SAC selector type: " + sac.getSelectorType() + " (" + sac + ")"); //$NON-NLS-1$ //$NON-NLS-2$ + }; + } + + private static Selector translateSimple(SimpleSelector simple) { + return translate(simple); + } + + private static Selector translateElement(ElementSelector sel) { + String name = sel.getLocalName(); + return name == null ? new Universal() : new ElementType(name); + } + + private static Selector translatePseudoElement(ElementSelector sel) { + // Pseudo-element form (::first-line). The engine has never matched + // these; treat as the element it appears on (if any) or universal. + // In practice the parser only emits this when a stylesheet uses :: , + // which the supported subset does not. + String name = sel.getLocalName(); + return name == null ? new Universal() : new PseudoClass(name); + } + + private static Selector translateConditional(ConditionalSelector sel) { + Selector left = translate(sel.getSimpleSelector()); + Selector right = translateCondition(sel.getCondition()); + if (left instanceof Universal) { + return right; + } + return new And(left, right); + } + + private static Selector translateCondition(Condition condition) { + return switch (condition.getConditionType()) { + case Condition.SAC_CLASS_CONDITION -> new ClassSelector(((AttributeCondition) condition).getValue()); + case Condition.SAC_ID_CONDITION -> new IdSelector(((AttributeCondition) condition).getValue()); + case Condition.SAC_PSEUDO_CLASS_CONDITION -> new PseudoClass(((AttributeCondition) condition).getValue()); + case Condition.SAC_LANG_CONDITION -> { + // Modeled as a presence-form attribute selector keyed on lang; + // nothing in the supported subset uses :lang(), but the parser + // can still emit it. + AttributeCondition lang = (AttributeCondition) condition; + yield new AttributeSelector("lang", lang.getValue()); //$NON-NLS-1$ + } + case Condition.SAC_ATTRIBUTE_CONDITION -> { + AttributeCondition attr = (AttributeCondition) condition; + // Batik's stock Parser always calls createAttributeCondition with + // specified=false, regardless of whether the source was [attr] or + // [attr='value']. Distinguish the two by whether a value was + // supplied: null means the presence form [attr]. + yield new AttributeSelector(attr.getLocalName(), attr.getValue()); + } + case Condition.SAC_ONE_OF_ATTRIBUTE_CONDITION -> { + AttributeCondition attr = (AttributeCondition) condition; + yield new AttributeIncludes(attr.getLocalName(), attr.getValue()); + } + case Condition.SAC_BEGIN_HYPHEN_ATTRIBUTE_CONDITION -> { + AttributeCondition attr = (AttributeCondition) condition; + yield new AttributeBeginHyphen(attr.getLocalName(), attr.getValue()); + } + case Condition.SAC_AND_CONDITION -> { + CombinatorCondition combo = (CombinatorCondition) condition; + yield new And(translateCondition(combo.getFirstCondition()), + translateCondition(combo.getSecondCondition())); + } + default -> throw new IllegalArgumentException( + "Unsupported SAC condition type: " + condition.getConditionType() + " (" + condition + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + }; + } +} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/selector/SelectorMatcher.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/selector/SelectorMatcher.java new file mode 100644 index 00000000000..cb13fd402ea --- /dev/null +++ b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/selector/SelectorMatcher.java @@ -0,0 +1,262 @@ +/******************************************************************************* + * Copyright (c) 2026 Lars Vogel 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: + * Lars Vogel - initial API and implementation + *******************************************************************************/ +package org.eclipse.e4.ui.css.core.impl.engine.selector; + +import org.eclipse.e4.ui.css.core.dom.CSSStylableElement; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Adjacent; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.And; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.AttributeBeginHyphen; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.AttributeIncludes; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.AttributeSelector; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Child; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.ClassSelector; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Descendant; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.ElementType; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.IdSelector; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.PseudoClass; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Selector; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.SelectorList; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Universal; +import org.w3c.dom.Element; +import org.w3c.dom.Node; + +/** + * Matches a {@link Selector} against an {@link Element}. + * + *

+ * Every method is static; the matcher carries no state. Callers pass the + * element being tested plus an optional pseudo-element string (the same + * argument the SAC engine carried) so that pseudo-class matching can defer + * to the existing {@link CSSStylableElement#isPseudoInstanceOf} contract. + *

+ * + *

+ * Tag-name comparison is case sensitive, matching the existing SAC matcher + * (Phase 1 test {@code testTagNameCaseSensitivity} in {@code CSSEngineTest} + * locks this in). Pseudo-class semantics also follow the existing engine: + * the static-pseudo-instance carve-out from + * {@code CSSPseudoClassConditionImpl} is preserved so cascade behaviour + * does not shift. + *

+ */ +public final class SelectorMatcher { + + private SelectorMatcher() { + // statics only + } + + /** + * @return {@code true} if {@code selector} matches {@code element} for + * the given pseudo state. + */ + public static boolean matches(Selector selector, Element element, String pseudoElement) { + if (element == null) { + return false; + } + if (selector instanceof Universal) { + return true; + } + if (selector instanceof ElementType type) { + return matchesElementType(type, element); + } + if (selector instanceof ClassSelector cls) { + return matchesClass(cls, element); + } + if (selector instanceof IdSelector id) { + return matchesId(id, element); + } + if (selector instanceof AttributeSelector attr) { + return matchesAttribute(attr, element); + } + if (selector instanceof AttributeIncludes inc) { + return matchesAttributeIncludes(inc, element); + } + if (selector instanceof AttributeBeginHyphen beg) { + return matchesAttributeBeginHyphen(beg, element); + } + if (selector instanceof PseudoClass pc) { + return matchesPseudoClass(pc, element, pseudoElement); + } + if (selector instanceof And and) { + return matches(and.left(), element, pseudoElement) && matches(and.right(), element, pseudoElement); + } + if (selector instanceof Descendant d) { + return matchesDescendant(d, element, pseudoElement); + } + if (selector instanceof Child c) { + return matchesChild(c, element, pseudoElement); + } + if (selector instanceof Adjacent a) { + return matchesAdjacent(a, element, pseudoElement); + } + if (selector instanceof SelectorList list) { + return matchesAny(list, element, pseudoElement); + } + throw new IllegalStateException("Unknown selector kind: " + selector.getClass()); //$NON-NLS-1$ + } + + private static boolean matchesElementType(ElementType type, Element element) { + String localName = type.localName(); + if (localName == null) { + return true; + } + String elementName = element.getPrefix() == null ? element.getNodeName() : element.getLocalName(); + return localName.equals(elementName); + } + + private static boolean matchesClass(ClassSelector cls, Element element) { + if (!(element instanceof CSSStylableElement stylable)) { + return false; + } + String elementClass = stylable.getCSSClass(); + if (elementClass == null) { + return false; + } + // CSS class attribute can be a whitespace-separated list of classes. + // Walk the string manually to avoid the regex compile + array allocation + // String.split forces on every match evaluation. + return containsWord(elementClass, cls.className()); + } + + private static boolean matchesId(IdSelector id, Element element) { + if (!(element instanceof CSSStylableElement stylable)) { + return false; + } + return id.id().equals(stylable.getCSSId()); + } + + private static boolean matchesAttribute(AttributeSelector attr, Element element) { + String name = attr.name(); + if (!element.hasAttribute(name)) { + return false; + } + String required = attr.value(); + if (required == null) { + // presence form: [attr] + return true; + } + return required.equals(element.getAttribute(name)); + } + + private static boolean matchesAttributeIncludes(AttributeIncludes inc, Element element) { + String actual = element.getAttribute(inc.name()); + if (actual == null) { + return false; + } + return containsWord(actual, inc.value()); + } + + /** + * Returns {@code true} if {@code haystack} contains {@code word} as a + * whitespace-separated token. Equivalent to splitting on + * {@code \s+} and checking for an exact token match, but without the + * regex compile and array allocation each call. + */ + private static boolean containsWord(String haystack, String word) { + if (word == null || word.isEmpty()) { + return false; + } + int wordLength = word.length(); + int length = haystack.length(); + int i = 0; + while (i < length) { + while (i < length && Character.isWhitespace(haystack.charAt(i))) { + i++; + } + int start = i; + while (i < length && !Character.isWhitespace(haystack.charAt(i))) { + i++; + } + if (i - start == wordLength && haystack.regionMatches(start, word, 0, wordLength)) { + return true; + } + } + return false; + } + + private static boolean matchesAttributeBeginHyphen(AttributeBeginHyphen beg, Element element) { + String actual = element.getAttribute(beg.name()); + if (actual == null) { + return false; + } + String value = beg.value(); + return actual.equals(value) || actual.startsWith(value + "-"); + } + + private static boolean matchesPseudoClass(PseudoClass pseudo, Element element, String pseudoElement) { + String name = pseudo.name(); + // If the caller is iterating a static-pseudo cascade, only match the + // pseudo argument on the way down. + if (pseudoElement != null && !pseudoElement.equals(name)) { + return false; + } + if (!(element instanceof CSSStylableElement stylable)) { + return false; + } + if (!stylable.isPseudoInstanceOf(name)) { + return false; + } + if (pseudoElement == null) { + // Same carve-out as CSSPseudoClassConditionImpl: when no pseudo + // element argument is supplied, pseudos that the element + // publishes only as static instances do not match the regular + // cascade. They get applied separately via the default style + // declaration map. + return !stylable.isStaticPseudoInstance(name); + } + return true; + } + + private static boolean matchesDescendant(Descendant d, Element element, String pseudoElement) { + if (!matches(d.descendant(), element, pseudoElement)) { + return false; + } + Node parent = element.getParentNode(); + while (parent instanceof Element parentElement) { + if (matches(d.ancestor(), parentElement, null)) { + return true; + } + parent = parentElement.getParentNode(); + } + return false; + } + + private static boolean matchesChild(Child c, Element element, String pseudoElement) { + if (!matches(c.child(), element, pseudoElement)) { + return false; + } + Node parent = element.getParentNode(); + return parent instanceof Element parentElement && matches(c.parent(), parentElement, null); + } + + private static boolean matchesAdjacent(Adjacent a, Element element, String pseudoElement) { + if (!matches(a.second(), element, pseudoElement)) { + return false; + } + Node previous = element.getPreviousSibling(); + while (previous != null && !(previous instanceof Element)) { + previous = previous.getPreviousSibling(); + } + return previous instanceof Element previousElement && matches(a.first(), previousElement, null); + } + + private static boolean matchesAny(SelectorList list, Element element, String pseudoElement) { + for (Selector alternative : list.alternatives()) { + if (matches(alternative, element, pseudoElement)) { + return true; + } + } + return false; + } +} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/selector/Selectors.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/selector/Selectors.java new file mode 100644 index 00000000000..ebca60b165b --- /dev/null +++ b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/selector/Selectors.java @@ -0,0 +1,357 @@ +/******************************************************************************* + * Copyright (c) 2026 Lars Vogel 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: + * Lars Vogel - initial API and implementation + *******************************************************************************/ +package org.eclipse.e4.ui.css.core.impl.engine.selector; + +import java.util.List; + +/** + * Internal CSS selector AST. + * + *

+ * The engine historically exposed W3C SAC selectors + * ({@code org.w3c.css.sac.Selector} and friends) and matched against them + * through a hierarchy of vendored Batik wrapper classes under + * {@code impl/sac/*}. This package replaces both with a small set of records + * that the engine owns end to end. The W3C SAC types stay only as long as + * the parser still emits them; a translator turns the SAC selector tree + * produced by the Batik SAC parser into one of these records before it + * reaches the engine matcher. + *

+ * + *

+ * Specificity follows CSS 2.1: 100 per id, 10 per class / attribute / + * pseudo-class, 1 per element, 0 for the universal selector. Combinators + * sum the specificity of their operands; selector lists report the maximum + * specificity over their alternatives. + *

+ */ +public final class Selectors { + + private Selectors() { + // constants only + } + + /** A parsed CSS selector. Sealed; pattern-match in the matcher. */ + public sealed interface Selector + permits Universal, ElementType, ClassSelector, IdSelector, AttributeSelector, + AttributeIncludes, AttributeBeginHyphen, PseudoClass, And, Descendant, Child, Adjacent, SelectorList { + + /** CSS specificity contribution of this selector. */ + int specificity(); + + /** Best-effort textual reproduction of the selector. */ + String text(); + } + + /** {@code *} — matches any element. */ + public record Universal() implements Selector { + @Override + public int specificity() { + return 0; + } + + @Override + public String text() { + return "*"; //$NON-NLS-1$ + } + + @Override + public String toString() { + return text(); + } + } + + /** {@code Button} — matches by local element name. */ + public record ElementType(String localName) implements Selector { + @Override + public int specificity() { + return 1; + } + + @Override + public String text() { + return localName; + } + + @Override + public String toString() { + return text(); + } + } + + /** {@code .foo} — matches by CSS class. */ + public record ClassSelector(String className) implements Selector { + @Override + public int specificity() { + return 10; + } + + @Override + public String text() { + return "." + className; //$NON-NLS-1$ + } + + @Override + public String toString() { + return text(); + } + } + + /** {@code #foo} — matches by CSS id. */ + public record IdSelector(String id) implements Selector { + @Override + public int specificity() { + return 100; + } + + @Override + public String text() { + return "#" + id; //$NON-NLS-1$ + } + + @Override + public String toString() { + return text(); + } + } + + /** + * {@code [attr]} or {@code [attr='value']}. {@code value} is {@code null} + * for the presence form and the empty string for {@code [attr='']}. + */ + public record AttributeSelector(String name, String value) implements Selector { + @Override + public int specificity() { + return 10; + } + + @Override + public String text() { + return value == null ? "[" + name + "]" : "[" + name + "='" + value + "']"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ + } + + @Override + public String toString() { + return text(); + } + } + + /** {@code [attr~='value']} — matches when {@code attr} contains {@code value} as a whitespace-separated word. */ + public record AttributeIncludes(String name, String value) implements Selector { + @Override + public int specificity() { + return 10; + } + + @Override + public String text() { + return "[" + name + "~='" + value + "']"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + @Override + public String toString() { + return text(); + } + } + + /** {@code [attr|='value']} — matches when {@code attr} equals {@code value} or starts with {@code value-}. */ + public record AttributeBeginHyphen(String name, String value) implements Selector { + @Override + public int specificity() { + return 10; + } + + @Override + public String text() { + return "[" + name + "|='" + value + "']"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + } + + @Override + public String toString() { + return text(); + } + } + + /** + * {@code :name} — matches when the element answers true to + * {@code isPseudoInstanceOf(name)} on its CSS-stylable element wrapper. + */ + public record PseudoClass(String name) implements Selector { + @Override + public int specificity() { + return 10; + } + + @Override + public String text() { + return ":" + name; //$NON-NLS-1$ + } + + @Override + public String toString() { + return text(); + } + } + + /** + * Compound selector: every operand must match the same element. Built + * by the translator for forms like {@code Button.primary#go} or + * {@code [a][b]} where multiple simple selectors apply to one element. + */ + public record And(Selector left, Selector right) implements Selector { + @Override + public int specificity() { + return left.specificity() + right.specificity(); + } + + @Override + public String text() { + return left.text() + right.text(); + } + + @Override + public String toString() { + return text(); + } + } + + /** {@code ancestor descendant} — descendant combinator. */ + public record Descendant(Selector ancestor, Selector descendant) implements Selector { + @Override + public int specificity() { + return ancestor.specificity() + descendant.specificity(); + } + + @Override + public String text() { + return ancestor.text() + " " + descendant.text(); //$NON-NLS-1$ + } + + @Override + public String toString() { + return text(); + } + } + + /** {@code parent > child} — child combinator. */ + public record Child(Selector parent, Selector child) implements Selector { + @Override + public int specificity() { + return parent.specificity() + child.specificity(); + } + + @Override + public String text() { + return parent.text() + " > " + child.text(); //$NON-NLS-1$ + } + + @Override + public String toString() { + return text(); + } + } + + /** {@code first + second} — direct adjacent sibling combinator. */ + public record Adjacent(Selector first, Selector second) implements Selector { + @Override + public int specificity() { + return first.specificity() + second.specificity(); + } + + @Override + public String text() { + return first.text() + " + " + second.text(); //$NON-NLS-1$ + } + + @Override + public String toString() { + return text(); + } + } + + /** + * {@code a, b} — selector list. Specificity reports the maximum over the + * alternatives so cascade ordering can match a list against an element by + * iterating its alternatives. + * + *

+ * A regular final class rather than a record because the cascade reads + * {@link #specificity()} once per matched alternative and we want it + * precomputed; record components cannot host derived state. + *

+ */ + public static final class SelectorList implements Selector { + + private final List alternatives; + private final int specificity; + + public SelectorList(List alternatives) { + this.alternatives = List.copyOf(alternatives); + int max = 0; + for (Selector alternative : this.alternatives) { + int s = alternative.specificity(); + if (s > max) { + max = s; + } + } + this.specificity = max; + } + + public List alternatives() { + return alternatives; + } + + /** Number of alternatives in the list. SAC-style accessor for callers iterating the list. */ + public int getLength() { + return alternatives.size(); + } + + /** {@code i}-th alternative. SAC-style accessor for callers iterating the list. */ + public Selector item(int i) { + return alternatives.get(i); + } + + @Override + public int specificity() { + return specificity; + } + + @Override + public String text() { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < alternatives.size(); i++) { + if (i > 0) { + sb.append(", "); //$NON-NLS-1$ + } + sb.append(alternatives.get(i).text()); + } + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + return o instanceof SelectorList other && alternatives.equals(other.alternatives); + } + + @Override + public int hashCode() { + return alternatives.hashCode(); + } + + @Override + public String toString() { + return text(); + } + } +} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractAttributeCondition.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractAttributeCondition.java deleted file mode 100644 index fabffbad828..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractAttributeCondition.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - - Copyright 2002, 2014 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Objects; -import org.w3c.css.sac.AttributeCondition; - -/** - * This class provides an abstract implementation of the {@link - * org.w3c.css.sac.AttributeCondition} interface. - */ -public abstract class AbstractAttributeCondition implements AttributeCondition, - ExtendedCondition { - - /** - * The attribute value. - */ - protected String value; - - /** - * Creates a new AbstractAttributeCondition object. - */ - protected AbstractAttributeCondition(String value) { - this.value = value; - } - - /** - * Indicates whether some other object is "equal to" this one. - * - * @param obj - * the reference object with which to compare. - */ - @Override - public boolean equals(Object obj) { - if (obj == null || (obj.getClass() != getClass())) { - return false; - } - AbstractAttributeCondition c = (AbstractAttributeCondition) obj; - return c.value.equals(value); - } - - /** - * equal objects should have equal hashCodes. - * - * @return hashCode of this AbstractAttributeCondition - */ - @Override - public int hashCode() { - return Objects.hashCode(value); - } - - /** - * Returns the specificity of this condition. - */ - @Override - public int getSpecificity() { - return 1 << 8; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.AttributeCondition#getValue()}. - */ - @Override - public String getValue() { - return value; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractCombinatorCondition.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractCombinatorCondition.java deleted file mode 100644 index 0de6c81b86c..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractCombinatorCondition.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - - Copyright 2002, 2014 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import org.w3c.css.sac.CombinatorCondition; -import org.w3c.css.sac.Condition; - -/** - * This class provides an abstract implementation of the {@link - * org.w3c.css.sac.CombinatorCondition} interface. - */ -public abstract class AbstractCombinatorCondition implements - CombinatorCondition, ExtendedCondition { - - /** - * The first condition. - */ - protected Condition firstCondition; - - /** - * The second condition. - */ - protected Condition secondCondition; - - /** - * Creates a new CombinatorCondition object. - */ - protected AbstractCombinatorCondition(Condition c1, Condition c2) { - firstCondition = c1; - secondCondition = c2; - } - - /** - * Indicates whether some other object is "equal to" this one. - * - * @param obj - * the reference object with which to compare. - */ - @Override - public boolean equals(Object obj) { - if (obj == null || (obj.getClass() != getClass())) { - return false; - } - AbstractCombinatorCondition c = (AbstractCombinatorCondition) obj; - return (c.firstCondition.equals(firstCondition) && c.secondCondition - .equals(secondCondition)); - } - - /** - * Returns the specificity of this condition. - */ - @Override - public int getSpecificity() { - return ((ExtendedCondition) getFirstCondition()).getSpecificity() - + ((ExtendedCondition) getSecondCondition()).getSpecificity(); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.CombinatorCondition#getFirstCondition()}. - */ - @Override - public Condition getFirstCondition() { - return firstCondition; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.CombinatorCondition#getSecondCondition()}. - */ - @Override - public Condition getSecondCondition() { - return secondCondition; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractDescendantSelector.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractDescendantSelector.java deleted file mode 100644 index f183af1ccae..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractDescendantSelector.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - - Copyright 2002, 2014 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import org.w3c.css.sac.DescendantSelector; -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SimpleSelector; - -/** - * This class provides an abstract implementation of the {@link - * org.w3c.css.sac.DescendantSelector} interface. - */ -public abstract class AbstractDescendantSelector - implements DescendantSelector, - ExtendedSelector { - - /** - * The ancestor selector. - */ - protected Selector ancestorSelector; - - /** - * The simple selector. - */ - protected SimpleSelector simpleSelector; - - /** - * Creates a new DescendantSelector object. - */ - protected AbstractDescendantSelector(Selector ancestor, - SimpleSelector simple) { - ancestorSelector = ancestor; - simpleSelector = simple; - } - - /** - * Indicates whether some other object is "equal to" this one. - * @param obj the reference object with which to compare. - */ - @Override - public boolean equals(Object obj) { - if (obj == null || (obj.getClass() != getClass())) { - return false; - } - AbstractDescendantSelector s = (AbstractDescendantSelector)obj; - return s.simpleSelector.equals(simpleSelector); - } - - /** - * Returns the specificity of this selector. - */ - @Override - public int getSpecificity() { - return ((ExtendedSelector)ancestorSelector).getSpecificity() + - ((ExtendedSelector)simpleSelector).getSpecificity(); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.DescendantSelector#getAncestorSelector()}. - */ - @Override - public Selector getAncestorSelector() { - return ancestorSelector; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.DescendantSelector#getSimpleSelector()}. - */ - @Override - public SimpleSelector getSimpleSelector() { - return simpleSelector; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractElementSelector.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractElementSelector.java deleted file mode 100644 index 529b86a0fbb..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractElementSelector.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Set; -import org.w3c.css.sac.ElementSelector; - -/** - * This class provides an abstract implementation of the ElementSelector - * interface. - */ -public abstract class AbstractElementSelector implements ElementSelector, ExtendedSelector { - - /** - * The namespace URI. - */ - protected String namespaceURI; - - /** - * The local name. - */ - protected String localName; - - /** - * Creates a new ElementSelector object. - */ - protected AbstractElementSelector(String uri, String name) { - namespaceURI = uri; - localName = name; - } - - /** - * Indicates whether some other object is "equal to" this one. - * @param obj the reference object with which to compare. - */ - @Override - public boolean equals(Object obj) { - if (obj == null || (obj.getClass() != getClass())) { - return false; - } - AbstractElementSelector s = (AbstractElementSelector)obj; - return (s.namespaceURI.equals(namespaceURI) && s.localName.equals(localName)); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.ElementSelector#getNamespaceURI()}. - */ - @Override - public String getNamespaceURI() { - return namespaceURI; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.ElementSelector#getLocalName()}. - */ - @Override - public String getLocalName() { - return localName; - } - - /** - * Fills the given set with the attribute names found in this selector. - */ - @Override - public void fillAttributeSet(Set attrSet) { - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractSiblingSelector.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractSiblingSelector.java deleted file mode 100644 index bb061dd13e0..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/AbstractSiblingSelector.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - - Copyright 2002, 2014 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SiblingSelector; -import org.w3c.css.sac.SimpleSelector; - -/** - * This class provides an abstract implementation of the {@link - * org.w3c.css.sac.SiblingSelector} interface. - */ -public abstract class AbstractSiblingSelector implements SiblingSelector, - ExtendedSelector { - - /** - * The node type. - */ - protected short nodeType; - - /** - * The selector. - */ - protected Selector selector; - - /** - * The simple selector. - */ - protected SimpleSelector simpleSelector; - - /** - * Creates a new SiblingSelector object. - */ - protected AbstractSiblingSelector(short type, Selector sel, - SimpleSelector simple) { - nodeType = type; - selector = sel; - simpleSelector = simple; - } - - /** - * Returns the node type. - */ - @Override - public short getNodeType() { - return nodeType; - } - - /** - * Indicates whether some other object is "equal to" this one. - * - * @param obj - * the reference object with which to compare. - */ - @Override - public boolean equals(Object obj) { - if (obj == null || (obj.getClass() != getClass())) { - return false; - } - AbstractSiblingSelector s = (AbstractSiblingSelector) obj; - return s.simpleSelector.equals(simpleSelector); - } - - /** - * Returns the specificity of this selector. - */ - @Override - public int getSpecificity() { - return ((ExtendedSelector) selector).getSpecificity() - + ((ExtendedSelector) simpleSelector).getSpecificity(); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.SiblingSelector#getSelector()}. - */ - @Override - public Selector getSelector() { - return selector; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.SiblingSelector#getSiblingSelector()}. - */ - @Override - public SimpleSelector getSiblingSelector() { - return simpleSelector; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSAndConditionImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSAndConditionImpl.java deleted file mode 100644 index b9d528d05ad..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSAndConditionImpl.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Set; -import org.w3c.css.sac.Condition; -import org.w3c.dom.Element; - -/** - * This class provides an implementation of the - * {@link org.w3c.css.sac.CombinatorCondition} interface. - */ -public class CSSAndConditionImpl extends AbstractCombinatorCondition { - /** - * Creates a new CombinatorCondition object. - */ - public CSSAndConditionImpl(Condition c1, Condition c2) { - super(c1, c2); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Condition#getConditionType()}. - */ - @Override - public short getConditionType() { - return SAC_AND_CONDITION; - } - - /** - * Tests whether this condition matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - return ((ExtendedCondition)getFirstCondition()).match(e, pseudoE) && - ((ExtendedCondition)getSecondCondition()).match(e, pseudoE); - } - - /** - * Fills the given set with the attribute names found in this selector. - */ - @Override - public void fillAttributeSet(Set attrSet) { - ((ExtendedCondition)getFirstCondition()).fillAttributeSet(attrSet); - ((ExtendedCondition)getSecondCondition()).fillAttributeSet(attrSet); - } - - /** - * Returns a text representation of this object. - */ - @Override - public String toString() { - return String.valueOf( getFirstCondition() ) + getSecondCondition(); - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSAttributeConditionImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSAttributeConditionImpl.java deleted file mode 100644 index 3467d068938..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSAttributeConditionImpl.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Set; -import org.w3c.dom.Element; - -/** - * This class provides an implementation of the - * {@link org.w3c.css.sac.AttributeCondition} interface. - */ -public class CSSAttributeConditionImpl extends AbstractAttributeCondition { - /** - * The attribute's local name. - */ - protected String localName; - - /** - * The attribute's namespace URI. - */ - protected String namespaceURI; - - /** - * Whether this condition applies to specified attributes. - */ - protected boolean specified; - - /** - * Creates a new CSSAttributeCondition object. - */ - public CSSAttributeConditionImpl(String localName, String namespaceURI, - boolean specified, String value) { - super(value); - this.localName = localName; - this.namespaceURI = namespaceURI; - this.specified = specified; - } - - /** - * Indicates whether some other object is "equal to" this one. - * - * @param obj - * the reference object with which to compare. - */ - @Override - public boolean equals(Object obj) { - if (!super.equals(obj)) { - return false; - } - CSSAttributeConditionImpl c = (CSSAttributeConditionImpl) obj; - return (c.namespaceURI.equals(namespaceURI) - && c.localName.equals(localName) && c.specified == specified); - } - - /** - * equal objects should have equal hashCodes. - * - * @return hashCode of this CSSAttributeCondition - */ - @Override - public int hashCode() { - return namespaceURI.hashCode() ^ localName.hashCode() - ^ Boolean.hashCode(specified); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Condition#getConditionType()}. - */ - @Override - public short getConditionType() { - return SAC_ATTRIBUTE_CONDITION; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.AttributeCondition#getNamespaceURI()}. - */ - @Override - public String getNamespaceURI() { - return namespaceURI; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.AttributeCondition#getLocalName()}. - */ - @Override - public String getLocalName() { - return localName; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.AttributeCondition#getSpecified()}. - */ - @Override - public boolean getSpecified() { - return specified; - } - - /** - * Tests whether this condition matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - if (!e.hasAttribute(getLocalName())) { - return false; - } - String val = getValue(); - if (val == null) { - return true; - } - return e.getAttribute(getLocalName()).equals(val); - } - - /** - * Fills the given set with the attribute names found in this selector. - */ - @Override - public void fillAttributeSet(Set attrSet) { - attrSet.add(localName); - } - - /** - * Returns a text representation of this object. - */ - @Override - public String toString() { - if (value == null) { - return '[' + localName + ']'; - } - return '[' + localName + "=\"" + value + "\"]"; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSBeginHyphenAttributeConditionImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSBeginHyphenAttributeConditionImpl.java deleted file mode 100644 index 12064bb8e47..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSBeginHyphenAttributeConditionImpl.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - - Copyright 2002, 2014 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import org.w3c.dom.Element; - -/** - * This class provides an implementation of the - * {@link org.w3c.css.sac.AttributeCondition} interface. - */ -public class CSSBeginHyphenAttributeConditionImpl extends - CSSAttributeConditionImpl { - - /** - * Creates a new CSSAttributeCondition object. - */ - public CSSBeginHyphenAttributeConditionImpl(String localName, - String namespaceURI, boolean specified, String value) { - super(localName, namespaceURI, specified, value); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Condition#getConditionType()}. - */ - @Override - public short getConditionType() { - return SAC_BEGIN_HYPHEN_ATTRIBUTE_CONDITION; - } - - /** - * Tests whether this condition matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - return e.getAttribute(getLocalName()).startsWith(getValue()); - } - - /** - * Returns a text representation of this object. - */ - @Override - public String toString() { - return '[' + getLocalName() + "|=\"" + getValue() + "\"]"; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSChildSelectorImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSChildSelectorImpl.java deleted file mode 100644 index 450031025f2..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSChildSelectorImpl.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Set; -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SimpleSelector; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -/** - * This class provides an implementation of the - * {@link org.w3c.css.sac.DescendantSelector} interface. - */ -public class CSSChildSelectorImpl extends AbstractDescendantSelector { - - /** - * Creates a new CSSChildSelector object. - */ - public CSSChildSelectorImpl(Selector ancestor, SimpleSelector simple) { - super(ancestor, simple); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Selector#getSelectorType()}. - */ - @Override - public short getSelectorType() { - return SAC_CHILD_SELECTOR; - } - - /** - * Tests whether this selector matches the given element. - */ - @Override - public boolean match(Element e, Node[] hierarchy, int parentIndex, String pseudoE) { - if (hierarchy == null || parentIndex >= hierarchy.length) { - return false; - } - - Node n = hierarchy[parentIndex]; - if (n != null && n.getNodeType() == Node.ELEMENT_NODE) { - return ((ExtendedSelector) getAncestorSelector()).match((Element) n, hierarchy, parentIndex + 1, null) - && ((ExtendedSelector) getSimpleSelector()).match(e, hierarchy, parentIndex + 1, pseudoE); - } - return false; - } - - /** - * Tests whether this selector matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - Node n = e.getParentNode(); - if (n != null && n.getNodeType() == Node.ELEMENT_NODE) { - return ((ExtendedSelector) getAncestorSelector()).match((Element) n, - null) - && ((ExtendedSelector) getSimpleSelector()).match(e, pseudoE); - } - return false; - } - - /** - * Fills the given set with the attribute names found in this selector. - */ - @Override - public void fillAttributeSet(Set attrSet) { - ((ExtendedSelector)getAncestorSelector()).fillAttributeSet(attrSet); - ((ExtendedSelector)getSimpleSelector()).fillAttributeSet(attrSet); - } - - /** - * Returns a representation of the selector. - */ - @Override - public String toString() { - SimpleSelector s = getSimpleSelector(); - if (s.getSelectorType() == SAC_PSEUDO_ELEMENT_SELECTOR) { - return String.valueOf( getAncestorSelector() ) + s; - } - return getAncestorSelector() + " > " + s; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSClassConditionImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSClassConditionImpl.java deleted file mode 100644 index 521c7743af1..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSClassConditionImpl.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - - Copyright 2002, 2014 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import org.eclipse.e4.ui.css.core.dom.CSSStylableElement; -import org.w3c.dom.Element; - -/** - * This class provides an implementation of the - * {@link org.w3c.css.sac.AttributeCondition} interface. - */ -public class CSSClassConditionImpl extends CSSAttributeConditionImpl { - - /** - * Creates a new CSSAttributeCondition object. - */ - public CSSClassConditionImpl(String localName, String namespaceURI, - String value) { - super(localName, namespaceURI, true, value); - } - - @Override - public boolean match(Element e, String pseudoE) { - String attr = null; - if ((e instanceof CSSStylableElement)) { - attr = ((CSSStylableElement) e).getCSSClass(); - } else { - attr = e.getAttribute("class"); - } - if (attr == null || attr.length() < 1) { - return false; - } - String val = getValue(); - int attrLen = attr.length(); - int valLen = val.length(); - for (int i = attr.indexOf(val); i != -1; i = attr.indexOf(val, i - + valLen)) { - if ((i == 0 || Character.isSpaceChar(attr.charAt(i - 1))) - && (i + valLen == attrLen || Character.isSpaceChar(attr - .charAt(i + valLen)))) { - return true; - } - } - - return false; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSConditionFactoryImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSConditionFactoryImpl.java deleted file mode 100644 index 24676814228..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSConditionFactoryImpl.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - - Copyright 2002, 2014 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import org.w3c.css.sac.AttributeCondition; -import org.w3c.css.sac.CSSException; -import org.w3c.css.sac.CombinatorCondition; -import org.w3c.css.sac.Condition; -import org.w3c.css.sac.ConditionFactory; -import org.w3c.css.sac.ContentCondition; -import org.w3c.css.sac.LangCondition; -import org.w3c.css.sac.NegativeCondition; -import org.w3c.css.sac.PositionalCondition; - -/** - * This class provides an implementation of the - * {@link org.w3c.css.sac.ConditionFactory} interface. - */ -public class CSSConditionFactoryImpl implements ConditionFactory { - - private static final String NOT_IMPLEMENTED_IN_CSS2 = "Not implemented in CSS2"; //$NON-NLS-1$ - - /** - * The class attribute namespace URI. - */ - protected String classNamespaceURI; - - /** - * The class attribute local name. - */ - protected String classLocalName; - - /** - * The id attribute namespace URI. - */ - protected String idNamespaceURI; - - /** - * The id attribute local name. - */ - protected String idLocalName; - - /** - * Creates a new condition factory. - */ - public CSSConditionFactoryImpl(String cns, String cln, String idns, - String idln) { - classNamespaceURI = cns; - classLocalName = cln; - idNamespaceURI = idns; - idLocalName = idln; - } - - /** - * SAC: Implements {@link - * ConditionFactory#createAndCondition(Condition,Condition)}. - */ - @Override - public CombinatorCondition createAndCondition(Condition first, - Condition second) throws CSSException { - return new CSSAndConditionImpl(first, second); - } - - /** - * SAC: Implements {@link - * ConditionFactory#createOrCondition(Condition,Condition)}. - */ - @Override - public CombinatorCondition createOrCondition(Condition first, - Condition second) throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.ConditionFactory#createNegativeCondition(Condition)}. - */ - @Override - public NegativeCondition createNegativeCondition(Condition condition) - throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } - - /** - * SAC: Implements {@link - * ConditionFactory#createPositionalCondition(int,boolean,boolean)}. - */ - @Override - public PositionalCondition createPositionalCondition(int position, - boolean typeNode, boolean type) throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } - - /** - * SAC: Implements {@link - * ConditionFactory#createAttributeCondition(String,String,boolean,String)}. - */ - @Override - public AttributeCondition createAttributeCondition(String localName, - String namespaceURI, boolean specified, String value) - throws CSSException { - return new CSSAttributeConditionImpl(localName, namespaceURI, specified, - value); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.ConditionFactory#createIdCondition(String)}. - */ - @Override - public AttributeCondition createIdCondition(String value) - throws CSSException { - return new CSSIdConditionImpl(idNamespaceURI, idLocalName, value); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.ConditionFactory#createLangCondition(String)}. - */ - @Override - public LangCondition createLangCondition(String lang) throws CSSException { - return new CSSLangConditionImpl(lang); - } - - /** - * SAC: Implements {@link - * ConditionFactory#createOneOfAttributeCondition(String,String,boolean,String)}. - */ - @Override - public AttributeCondition createOneOfAttributeCondition(String localName, - String nsURI, boolean specified, String value) throws CSSException { - return new CSSOneOfAttributeConditionImpl(localName, nsURI, specified, - value); - } - - /** - * SAC: Implements {@link - * ConditionFactory#createBeginHyphenAttributeCondition(String,String,boolean,String)}. - */ - @Override - public AttributeCondition createBeginHyphenAttributeCondition( - String localName, String namespaceURI, boolean specified, - String value) throws CSSException { - return new CSSBeginHyphenAttributeConditionImpl(localName, - namespaceURI, specified, value); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.ConditionFactory#createClassCondition(String,String)}. - */ - @Override - public AttributeCondition createClassCondition(String namespaceURI, - String value) throws CSSException { - return new CSSClassConditionImpl(classLocalName, classNamespaceURI, value); - } - - /** - * SAC: Implements {@link - * ConditionFactory#createPseudoClassCondition(String,String)}. - */ - @Override - public AttributeCondition createPseudoClassCondition(String namespaceURI, - String value) throws CSSException { - return new CSSPseudoClassConditionImpl(namespaceURI, value); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.ConditionFactory#createOnlyChildCondition()}. - */ - @Override - public Condition createOnlyChildCondition() throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.ConditionFactory#createOnlyTypeCondition()}. - */ - @Override - public Condition createOnlyTypeCondition() throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.ConditionFactory#createContentCondition(String)}. - */ - @Override - public ContentCondition createContentCondition(String data) - throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSConditionalSelectorImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSConditionalSelectorImpl.java deleted file mode 100644 index c4cc09ad9c1..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSConditionalSelectorImpl.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Set; -import org.w3c.css.sac.Condition; -import org.w3c.css.sac.ConditionalSelector; -import org.w3c.css.sac.SimpleSelector; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -/** - * This class provides an implementation of the - * {@link org.w3c.css.sac.ConditionalSelector} interface. - */ -public class CSSConditionalSelectorImpl implements ConditionalSelector, ExtendedSelector { - - /** - * The simple selector. - */ - protected SimpleSelector simpleSelector; - - /** - * The condition. - */ - protected Condition condition; - - /** - * Creates a new ConditionalSelector object. - */ - public CSSConditionalSelectorImpl(SimpleSelector s, Condition c) { - simpleSelector = s; - condition = c; - } - - /** - * Indicates whether some other object is "equal to" this one. - * @param obj the reference object with which to compare. - */ - @Override - public boolean equals(Object obj) { - if (obj == null || (obj.getClass() != getClass())) { - return false; - } - CSSConditionalSelectorImpl s = (CSSConditionalSelectorImpl)obj; - return (s.simpleSelector.equals(simpleSelector) && - s.condition.equals(condition)); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Selector#getSelectorType()}. - */ - @Override - public short getSelectorType() { - return SAC_CONDITIONAL_SELECTOR; - } - - @Override - public boolean match(Element e, Node[] hierarchy, int parentIndex, String pseudoE) { - return ((ExtendedSelector)getSimpleSelector()).match(e, hierarchy, parentIndex, pseudoE) && - ((ExtendedCondition)getCondition()).match(e, pseudoE); - } - - /** - * Tests whether this selector matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - return ((ExtendedSelector)getSimpleSelector()).match(e, pseudoE) && - ((ExtendedCondition)getCondition()).match(e, pseudoE); - } - - /** - * Fills the given set with the attribute names found in this selector. - */ - @Override - public void fillAttributeSet(Set attrSet) { - ((ExtendedSelector)getSimpleSelector()).fillAttributeSet(attrSet); - ((ExtendedCondition)getCondition()).fillAttributeSet(attrSet); - } - - /** - * Returns the specificity of this selector. - */ - @Override - public int getSpecificity() { - return ((ExtendedSelector)getSimpleSelector()).getSpecificity() + - ((ExtendedCondition)getCondition()).getSpecificity(); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.ConditionalSelector#getSimpleSelector()}. - */ - @Override - public SimpleSelector getSimpleSelector() { - return simpleSelector; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.ConditionalSelector#getCondition()}. - */ - @Override - public Condition getCondition() { - return condition; - } - - /** - * Returns a representation of the selector. - */ - @Override - public String toString() { - return String.valueOf( simpleSelector ) + condition; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSDescendantSelectorImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSDescendantSelectorImpl.java deleted file mode 100644 index 44f3568dfc3..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSDescendantSelectorImpl.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Set; -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SimpleSelector; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -/** - * This class provides an implementation for the - * {@link org.w3c.css.sac.DescendantSelector} interface. - */ -public class CSSDescendantSelectorImpl extends AbstractDescendantSelector { - - /** - * Creates a new CSSDescendantSelector object. - */ - public CSSDescendantSelectorImpl(Selector ancestor, SimpleSelector simple) { - super(ancestor, simple); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Selector#getSelectorType()}. - */ - @Override - public short getSelectorType() { - return SAC_DESCENDANT_SELECTOR; - } - - /** - * Tests whether this selector matches the given element. - */ - @Override - public boolean match(Element e, Node[] hierarchy, int parentIndex, String pseudoE) { - ExtendedSelector p = (ExtendedSelector) getAncestorSelector(); - if (!((ExtendedSelector) getSimpleSelector()).match(e, hierarchy, parentIndex, pseudoE)) { - return false; - } - - if (hierarchy == null) { - return false; - } - - Node n; - int length = hierarchy.length; - for (int i = parentIndex; i < length; i++) { - n = hierarchy[i]; - if (n != null && n.getNodeType() == Node.ELEMENT_NODE - && p.match((Element) n, hierarchy, i + 1, null)) { - return true; - } - } - return false; - } - - /** - * Tests whether this selector matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - ExtendedSelector p = (ExtendedSelector) getAncestorSelector(); - if (!((ExtendedSelector) getSimpleSelector()).match(e, pseudoE)) { - return false; - } - for (Node n = e.getParentNode(); n != null; n = n.getParentNode()) { - if (n.getNodeType() == Node.ELEMENT_NODE && p.match((Element) n, null)) { - return true; - } - } - return false; - } - - /** - * Fills the given set with the attribute names found in this selector. - */ - @Override - public void fillAttributeSet(Set attrSet) { - ((ExtendedSelector)getSimpleSelector()).fillAttributeSet(attrSet); - } - - /** - * Returns a representation of the selector. - */ - @Override - public String toString() { - return getAncestorSelector() + " " + getSimpleSelector(); - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSDirectAdjacentSelectorImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSDirectAdjacentSelectorImpl.java deleted file mode 100644 index 73eea3eb684..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSDirectAdjacentSelectorImpl.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Set; -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SimpleSelector; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -/** - * This class provides an implementation for the - * {@link org.w3c.css.sac.DescendantSelector} interface. - */ -public class CSSDirectAdjacentSelectorImpl extends AbstractSiblingSelector { - - /** - * Creates a new CSSDirectAdjacentSelector object. - */ - public CSSDirectAdjacentSelectorImpl(short type, Selector parent, SimpleSelector simple) { - super(type, parent, simple); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Selector#getSelectorType()}. - */ - @Override - public short getSelectorType() { - return SAC_DIRECT_ADJACENT_SELECTOR; - } - - /** - * Tests whether this selector matches the given element. - */ - @Override - public boolean match(Element e, Node[] hiearchy, int parentIndex, String pseudoE) { - Node n = e; - if (!((ExtendedSelector) getSiblingSelector()).match(e, hiearchy, parentIndex, pseudoE)) { - return false; - } - - while ((n = n.getPreviousSibling()) != null && n.getNodeType() != Node.ELEMENT_NODE) { - } - - if (n == null) { - return false; - } - - return ((ExtendedSelector) getSelector()).match((Element) n, hiearchy, parentIndex, null); - } - - /** - * Tests whether this selector matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - Node n = e; - if (!((ExtendedSelector)getSiblingSelector()).match(e, pseudoE)) { - return false; - } - while ((n = n.getPreviousSibling()) != null && n.getNodeType() != Node.ELEMENT_NODE) { - } - - if (n == null) { - return false; - } - - return ((ExtendedSelector)getSelector()).match((Element)n, null); - } - - /** - * Fills the given set with the attribute names found in this selector. - */ - @Override - public void fillAttributeSet(Set attrSet) { - ((ExtendedSelector)getSelector()).fillAttributeSet(attrSet); - ((ExtendedSelector)getSiblingSelector()).fillAttributeSet(attrSet); - } - - /** - * Returns a representation of the selector. - */ - @Override - public String toString() { - return getSelector() + " + " + getSiblingSelector(); - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSDocumentHandlerImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSDocumentHandlerImpl.java index 150c95c4b7a..9456b6a94e2 100644 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSDocumentHandlerImpl.java +++ b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSDocumentHandlerImpl.java @@ -25,6 +25,7 @@ import org.eclipse.e4.ui.css.core.impl.dom.CSSUnknownRuleImpl; import org.eclipse.e4.ui.css.core.impl.dom.CSSValueFactory; import org.eclipse.e4.ui.css.core.impl.dom.MediaListImpl; +import org.eclipse.e4.ui.css.core.impl.engine.selector.SacTranslator; import org.eclipse.e4.ui.css.core.sac.ExtendedDocumentHandler; import org.w3c.css.sac.CSSException; import org.w3c.css.sac.InputSource; @@ -165,9 +166,10 @@ public void endFontFace() throws CSSException { @Override public void startSelector(SelectorList selectors) throws CSSException { - // Create the style rule and add it to the rule list + // Translate the SAC selector list into the engine's internal AST at + // this boundary so nothing downstream needs to touch SAC types. CSSStyleRuleImpl rule = new CSSStyleRuleImpl(parentStyleSheet, null, - selectors); + SacTranslator.translate(selectors)); if (!getNodeStack().empty()) { ((CSSRuleListImpl) getNodeStack().peek()).add(rule); } diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSElementSelectorImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSElementSelectorImpl.java deleted file mode 100644 index f24e1aeea7f..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSElementSelectorImpl.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - - Copyright 2002, 2014 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import org.w3c.dom.Element; - -/** - * This class implements the {@link org.w3c.css.sac.ElementSelector} interface. - */ -public class CSSElementSelectorImpl extends AbstractElementSelector { - - /** - * Creates a new ElementSelector object. - */ - public CSSElementSelectorImpl(String uri, String name) { - super(uri, name); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Selector#getSelectorType()}. - */ - @Override - public short getSelectorType() { - return SAC_ELEMENT_NODE_SELECTOR; - } - - /** - * Tests whether this selector matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - String name = getLocalName(); - if (name == null) { - if (namespaceURI != null) { - return namespaceURI.equals(e.getNamespaceURI()); - } else { - return true; - } - } - String eName; - if (e.getPrefix() == null) { - eName = e.getNodeName(); - } else { - eName = e.getLocalName(); - } - // According to CSS 2 section 5.1 element - // names in selectors are case-sensitive for XML. - if (eName.equals(name)) { - if (namespaceURI != null) { - return namespaceURI.equals(e.getNamespaceURI()); - } else { - return true; - } - } - return false; - // For HTML - // return eName.equalsIgnoreCase(name); - } - - /** - * Returns the specificity of this selector. - */ - @Override - public int getSpecificity() { - return (getLocalName() == null) ? 0 : 1; - } - - /** - * Returns a representation of the selector. - */ - @Override - public String toString() { - String name = getLocalName(); - if (name == null) { - return "*"; - } - return name; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSIdConditionImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSIdConditionImpl.java deleted file mode 100644 index eec162ea03d..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSIdConditionImpl.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Set; -import org.eclipse.e4.ui.css.core.dom.CSSStylableElement; -import org.w3c.dom.Element; - -/** - * This class provides an implementation of the - * {@link org.w3c.css.sac.AttributeCondition} interface. - */ -public class CSSIdConditionImpl extends AbstractAttributeCondition { - - /** - * The id attribute namespace URI. - */ - protected String namespaceURI; - - /** - * The id attribute local name. - */ - protected String localName; - - /** - * Creates a new CSSAttributeCondition object. - */ - public CSSIdConditionImpl(String ns, String ln, String value) { - super(value); - namespaceURI = ns; - localName = ln; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Condition#getConditionType()}. - */ - @Override - public short getConditionType() { - return SAC_ID_CONDITION; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.AttributeCondition#getNamespaceURI()}. - */ - @Override - public String getNamespaceURI() { - return namespaceURI; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.AttributeCondition#getLocalName()}. - */ - @Override - public String getLocalName() { - return localName; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.AttributeCondition#getSpecified()}. - */ - @Override - public boolean getSpecified() { - return true; - } - - /** - * Tests whether this condition matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - String id = null; - if (e instanceof CSSStylableElement) { - id = ((CSSStylableElement) e).getCSSId(); - } else { - id = e.getAttribute("id"); - } - if (id == null) { - return false; - } - return id.equals(getValue()); - // return super.match(e, pseudoE); - } - - /** - * Fills the given set with the attribute names found in this selector. - */ - @Override - public void fillAttributeSet(Set attrSet) { - attrSet.add(localName); - } - - /** - * Returns the specificity of this condition. - */ - @Override - public int getSpecificity() { - return 1 << 16; - } - - /** - * Returns a text representation of this object. - */ - @Override - public String toString() { - return '#' + getValue(); - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSLangConditionImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSLangConditionImpl.java deleted file mode 100644 index 4789469f7fa..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSLangConditionImpl.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Set; -import org.w3c.css.sac.LangCondition; -import org.w3c.dom.Element; - -/** - * This class provides an implementation of the - * {@link org.w3c.css.sac.LangCondition} interface. - */ -public class CSSLangConditionImpl implements LangCondition, ExtendedCondition { - /** - * The language. - */ - protected String lang; - - /** - * The language with a hyphen suffixed. - */ - protected String langHyphen; - - /** - * Creates a new LangCondition object. - */ - public CSSLangConditionImpl(String lang) { - this.lang = lang.toLowerCase(); - this.langHyphen = lang + '-'; - } - - /** - * Indicates whether some other object is "equal to" this one. - * @param obj the reference object with which to compare. - */ - @Override - public boolean equals(Object obj) { - if (obj == null || (obj.getClass() != getClass())) { - return false; - } - CSSLangConditionImpl c = (CSSLangConditionImpl)obj; - return c.lang.equals(lang); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Condition#getConditionType()}. - */ - @Override - public short getConditionType() { - return SAC_LANG_CONDITION; - } - - /** - * SAC: Implements {@link org.w3c.css.sac.LangCondition#getLang()}. - */ - @Override - public String getLang() { - return lang; - } - - /** - * Returns the specificity of this condition. - */ - @Override - public int getSpecificity() { - return 1 << 8; - } - - /** - * Tests whether this condition matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - String s = e.getAttribute("lang").toLowerCase(); - if (s.equals(lang) || s.startsWith(langHyphen)) { - return true; - } - // s = e.getAttributeNS(XMLConstants.XML_NAMESPACE_URI, - // XMLConstants.XML_LANG_ATTRIBUTE).toLowerCase(); - return s.equals(lang) || s.startsWith(langHyphen); - } - - /** - * Fills the given set with the attribute names found in this selector. - */ - @Override - public void fillAttributeSet(Set attrSet) { - attrSet.add("lang"); - } - - /** - * Returns a text representation of this object. - */ - @Override - public String toString() { - return ":lang(" + lang + ')'; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSOneOfAttributeConditionImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSOneOfAttributeConditionImpl.java deleted file mode 100644 index fb4ebbd0428..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSOneOfAttributeConditionImpl.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/******************************************************************************* - * Contributors: - * This class was copied from org.apache.batik.css.engine.sac - * Apache Batik project - initial API and implementation - * Alain Le Guennec - Bug 458334 - *******************************************************************************/ -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.StringTokenizer; -import org.w3c.dom.Element; - -/** - * This class provides an implementation of the - * {@link org.w3c.css.sac.AttributeCondition} interface. - */ -public class CSSOneOfAttributeConditionImpl extends CSSAttributeConditionImpl { - /** - * Creates a new CSSAttributeCondition object. - */ - public CSSOneOfAttributeConditionImpl(String localName, - String namespaceURI, boolean specified, String value) { - super(localName, namespaceURI, specified, value); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Condition#getConditionType()}. - */ - @Override - public short getConditionType() { - return SAC_ONE_OF_ATTRIBUTE_CONDITION; - } - - /** - * Tests whether this condition matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - String attr = e.getAttribute(getLocalName()); - String val = getValue(); - for (StringTokenizer tok = new StringTokenizer(attr); tok.hasMoreElements();) { - String candidate = tok.nextToken(); - if (val.equals(candidate)) { - return true; - } - } - return false; - } - - /** - * Returns a text representation of this object. - */ - @Override - public String toString() { - return "[" + getLocalName() + "~=\"" + getValue() + "\"]"; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSPseudoClassConditionImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSPseudoClassConditionImpl.java deleted file mode 100644 index 1c459d6637b..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSPseudoClassConditionImpl.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Set; -import org.eclipse.e4.ui.css.core.dom.CSSStylableElement; -import org.w3c.dom.Element; - -/** - * This class provides an implementation of the - * {@link org.w3c.css.sac.AttributeCondition} interface. - */ -public class CSSPseudoClassConditionImpl extends AbstractAttributeCondition { - /** - * The namespaceURI. - */ - protected String namespaceURI; - - /** - * Creates a new CSSAttributeCondition object. - */ - public CSSPseudoClassConditionImpl(String namespaceURI, String value) { - super(value); - this.namespaceURI = namespaceURI; - } - - /** - * Indicates whether some other object is "equal to" this one. - * - * @param obj - * the reference object with which to compare. - */ - @Override - public boolean equals(Object obj) { - if (!super.equals(obj)) { - return false; - } - CSSPseudoClassConditionImpl c = (CSSPseudoClassConditionImpl) obj; - return c.namespaceURI.equals(namespaceURI); - } - - /** - * equal objects should have equal hashCodes. - * - * @return hashCode of this CSSPseudoClassCondition - */ - @Override - public int hashCode() { - return namespaceURI.hashCode(); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Condition#getConditionType()}. - */ - @Override - public short getConditionType() { - return SAC_PSEUDO_CLASS_CONDITION; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.AttributeCondition#getNamespaceURI()}. - */ - @Override - public String getNamespaceURI() { - return namespaceURI; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.AttributeCondition#getLocalName()}. - */ - @Override - public String getLocalName() { - return null; - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.AttributeCondition#getSpecified()}. - */ - @Override - public boolean getSpecified() { - return false; - } - - /** - * Tests whether this selector matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - if (pseudoE != null && !pseudoE.equals(getValue())) { - // pseudo instance is filled, it is not valid. - return false; - } - if (!(e instanceof CSSStylableElement element)) { - return false; - } - boolean isPseudoInstanceOf = element.isPseudoInstanceOf(getValue()); - if (!isPseudoInstanceOf) { - return false; - } - if (pseudoE == null) { - // pseudo element is not filled. - // test if this CSSPseudoClassCondition is NOT a static pseudo - // instance - return (!element.isStaticPseudoInstance(getValue())); - } - return true; - } - - /** - * Fills the given set with the attribute names found in this selector. - */ - @Override - public void fillAttributeSet(Set attrSet) { - } - - /** - * Returns a text representation of this object. - */ - @Override - public String toString() { - return ":" + getValue(); - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSPseudoElementSelectorImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSPseudoElementSelectorImpl.java deleted file mode 100644 index 5353e2c4885..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSPseudoElementSelectorImpl.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - - Copyright 2002, 2014 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import org.w3c.dom.Element; - -/** - * This class implements the {@link org.w3c.css.sac.ElementSelector} interface. - */ -public class CSSPseudoElementSelectorImpl extends AbstractElementSelector { - - /** - * Creates a new CSSPseudoElementSelector object. - */ - public CSSPseudoElementSelectorImpl(String uri, String name) { - super(uri, name); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.Selector#getSelectorType()}. - */ - @Override - public short getSelectorType() { - return SAC_PSEUDO_ELEMENT_SELECTOR; - } - - /** - * Tests whether this selector matches the given element. - */ - @Override - public boolean match(Element e, String pseudoE) { - return getLocalName().equalsIgnoreCase(pseudoE); - } - - /** - * Returns the specificity of this selector. - */ - @Override - public int getSpecificity() { - return 0; - } - - /** - * Returns a representation of the selector. - */ - @Override - public String toString() { - return ":" + getLocalName(); - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSSelectorFactoryImpl.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSSelectorFactoryImpl.java deleted file mode 100644 index 00370115317..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/CSSSelectorFactoryImpl.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - - Copyright 2002, 2014 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import org.w3c.css.sac.CSSException; -import org.w3c.css.sac.CharacterDataSelector; -import org.w3c.css.sac.Condition; -import org.w3c.css.sac.ConditionalSelector; -import org.w3c.css.sac.DescendantSelector; -import org.w3c.css.sac.ElementSelector; -import org.w3c.css.sac.NegativeSelector; -import org.w3c.css.sac.ProcessingInstructionSelector; -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SelectorFactory; -import org.w3c.css.sac.SiblingSelector; -import org.w3c.css.sac.SimpleSelector; - -/** - * This class implements the {@link org.w3c.css.sac.SelectorFactory} interface. - */ -public class CSSSelectorFactoryImpl implements SelectorFactory { - - private static final String NOT_IMPLEMENTED_IN_CSS2 = "Not implemented in CSS2"; //$NON-NLS-1$ - - /** - * The instance of this class. - */ - public static final SelectorFactory INSTANCE = new CSSSelectorFactoryImpl(); - - /** - * This class does not need to be instantiated. - */ - protected CSSSelectorFactoryImpl() { - } - - /** - * SAC: Implements {@link - * SelectorFactory#createConditionalSelector(SimpleSelector,Condition)}. - */ - @Override - public ConditionalSelector createConditionalSelector( - SimpleSelector selector, Condition condition) throws CSSException { - return new CSSConditionalSelectorImpl(selector, condition); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.SelectorFactory#createAnyNodeSelector()}. - */ - @Override - public SimpleSelector createAnyNodeSelector() throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.SelectorFactory#createRootNodeSelector()}. - */ - @Override - public SimpleSelector createRootNodeSelector() throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.SelectorFactory#createNegativeSelector(SimpleSelector)}. - */ - @Override - public NegativeSelector createNegativeSelector(SimpleSelector selector) - throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.SelectorFactory#createElementSelector(String,String)}. - */ - @Override - public ElementSelector createElementSelector(String namespaceURI, - String tagName) throws CSSException { - return new CSSElementSelectorImpl(namespaceURI, tagName); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.SelectorFactory#createTextNodeSelector(String)}. - */ - @Override - public CharacterDataSelector createTextNodeSelector(String data) - throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.SelectorFactory#createCDataSectionSelector(String)}. - */ - @Override - public CharacterDataSelector createCDataSectionSelector(String data) - throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } - - /** - * SAC: Implements {@link - * SelectorFactory#createProcessingInstructionSelector(String,String)}. - */ - @Override - public ProcessingInstructionSelector createProcessingInstructionSelector( - String target, String data) throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } - - /** - * SAC: Implements {@link - * org.w3c.css.sac.SelectorFactory#createCommentSelector(String)}. - */ - @Override - public CharacterDataSelector createCommentSelector(String data) - throws CSSException { - throw new CSSException(NOT_IMPLEMENTED_IN_CSS2); - } - - /** - * SAC: Implements {@link - * SelectorFactory#createPseudoElementSelector(String,String)}. - */ - @Override - public ElementSelector createPseudoElementSelector(String namespaceURI, - String pseudoName) throws CSSException { - return new CSSPseudoElementSelectorImpl(namespaceURI, pseudoName); - } - - /** - * SAC: Implements {@link - * SelectorFactory#createDescendantSelector(Selector,SimpleSelector)}. - */ - @Override - public DescendantSelector createDescendantSelector(Selector parent, - SimpleSelector descendant) throws CSSException { - return new CSSDescendantSelectorImpl(parent, descendant); - } - - /** - * SAC: Implements {@link - * SelectorFactory#createChildSelector(Selector,SimpleSelector)}. - */ - @Override - public DescendantSelector createChildSelector(Selector parent, - SimpleSelector child) throws CSSException { - return new CSSChildSelectorImpl(parent, child); - } - - /** - * SAC: Implements {@link - * SelectorFactory#createDirectAdjacentSelector(short,Selector,SimpleSelector)}. - */ - @Override - public SiblingSelector createDirectAdjacentSelector(short nodeType, - Selector child, SimpleSelector directAdjacent) throws CSSException { - return new CSSDirectAdjacentSelectorImpl(nodeType, child, - directAdjacent); - } -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/ExtendedCondition.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/ExtendedCondition.java deleted file mode 100644 index edc785b6760..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/ExtendedCondition.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Set; -import org.w3c.css.sac.Condition; -import org.w3c.dom.Element; - -/** - * This interface provides additional features to the - * {@link org.w3c.css.sac.Condition} interface. - */ -public interface ExtendedCondition extends Condition { - - /** - * Tests whether this condition matches the given element. - */ - boolean match(Element e, String pseudoE); - - /** - * Returns the specificity of this condition. - */ - int getSpecificity(); - - /** - * Fills the given set with the attribute names found in this selector. - */ - void fillAttributeSet(Set attrSet); -} diff --git a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/ExtendedSelector.java b/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/ExtendedSelector.java deleted file mode 100644 index b30db5d0db1..00000000000 --- a/bundles/org.eclipse.e4.ui.css.core/src/org/eclipse/e4/ui/css/core/impl/sac/ExtendedSelector.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - - Copyright 2002, 2015 The Apache Software Foundation - - 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. - - */ - -/* This class copied from org.apache.batik.css.engine.sac */ - -package org.eclipse.e4.ui.css.core.impl.sac; - -import java.util.Set; -import org.w3c.css.sac.Selector; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -/** - * This interface extends the {@link org.w3c.css.sac.Selector}. - */ -public interface ExtendedSelector extends Selector { - - default boolean match(Element e, Node[] ancestors, int parentIndex, String pseudoE) { - return match(e, pseudoE); - } - - /** - * Tests whether this selector matches the given element. - */ - boolean match(Element e, String pseudoE); - - /** - * Returns the specificity of this selector. - */ - int getSpecificity(); - - /** - * Fills the given set with the attribute names found in this selector. - */ - void fillAttributeSet(Set attrSet); -} diff --git a/bundles/org.eclipse.e4.ui.css.swt/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.ui.css.swt/META-INF/MANIFEST.MF index 5e0d5acbcdb..dfd7801f32b 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/META-INF/MANIFEST.MF +++ b/bundles/org.eclipse.e4.ui.css.swt/META-INF/MANIFEST.MF @@ -31,7 +31,8 @@ Export-Package: org.eclipse.e4.ui.css.swt;x-friends:="org.eclipse.ui.workbench", org.eclipse.e4.ui.internal.css.swt.definition;x-friends:="org.eclipse.ui.workbench" Require-Bundle: org.eclipse.e4.ui.css.core;bundle-version="0.12.200", org.eclipse.swt;bundle-version="[3.133.0,4.0.0)", - org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)" + org.eclipse.core.runtime;bundle-version="[3.29.0,4.0.0)", + org.eclipse.emf.common;bundle-version="[2.45.0,3.0.0)" Bundle-RequiredExecutionEnvironment: JavaSE-21 Bundle-ActivationPolicy: lazy Import-Package: org.eclipse.jface.resource, diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/engine/AbstractCSSSWTEngineImpl.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/engine/AbstractCSSSWTEngineImpl.java deleted file mode 100644 index cb81d021c2d..00000000000 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/engine/AbstractCSSSWTEngineImpl.java +++ /dev/null @@ -1,141 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008, 2014 Angelo Zerr 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: - * Angelo Zerr - initial API and implementation - * IBM Corporation - *******************************************************************************/ -package org.eclipse.e4.ui.css.swt.engine; - -import org.eclipse.e4.ui.css.core.dom.CSSStylableElement; -import org.eclipse.e4.ui.css.core.engine.CSSElementContext; -import org.eclipse.e4.ui.css.core.impl.engine.CSSEngineImpl; -import org.eclipse.e4.ui.css.core.resources.IResourcesRegistry; -import org.eclipse.e4.ui.css.swt.dom.WidgetElement; -import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTColorConverterImpl; -import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTCursorConverterImpl; -import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTFontConverterImpl; -import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTFontDataConverterImpl; -import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTGradientConverterImpl; -import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTImageConverterImpl; -import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTRGBConverterImpl; -import org.eclipse.e4.ui.css.swt.resources.SWTResourceRegistryKeyFactory; -import org.eclipse.e4.ui.css.swt.resources.SWTResourcesRegistry; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Widget; -import org.w3c.dom.Element; - -/** - * CSS SWT Engine implementation which configure CSSEngineImpl to apply styles - * to SWT widgets. - */ -public abstract class AbstractCSSSWTEngineImpl extends CSSEngineImpl { - - protected Display display; - - public AbstractCSSSWTEngineImpl(Display display) { - this(display, false); - } - - public AbstractCSSSWTEngineImpl(Display display, boolean lazyApplyingStyles) { - this.display = display; - - /** Initialize SWT CSSValue converter * */ - - // Register SWT RGB CSSValue Converter - super.registerCSSValueConverter(CSSValueSWTRGBConverterImpl.INSTANCE); - // Register SWT Color CSSValue Converter - super.registerCSSValueConverter(CSSValueSWTColorConverterImpl.INSTANCE); - // Register SWT Gradient CSSValue Converter - super.registerCSSValueConverter(CSSValueSWTGradientConverterImpl.INSTANCE); - // Register SWT Cursor CSSValue Converter - super.registerCSSValueConverter(CSSValueSWTCursorConverterImpl.INSTANCE); - // Register SWT Font CSSValue Converter - super.registerCSSValueConverter(CSSValueSWTFontConverterImpl.INSTANCE); - // Register SWT FontData CSSValue Converter - super.registerCSSValueConverter(CSSValueSWTFontDataConverterImpl.INSTANCE); - // Register SWT Image CSSValue Converter - super.registerCSSValueConverter(CSSValueSWTImageConverterImpl.INSTANCE); - - if (lazyApplyingStyles) { - new CSSSWTApplyStylesListener(display, this); - } - - initializeCSSElementProvider(); - initializeCSSPropertyHandlers(); - - setResourceRegistryKeyFactory(new SWTResourceRegistryKeyFactory()); - } - - protected abstract void initializeCSSPropertyHandlers(); - - protected abstract void initializeCSSElementProvider(); - - @Override - public IResourcesRegistry getResourcesRegistry() { - IResourcesRegistry resourcesRegistry = super.getResourcesRegistry(); - if (resourcesRegistry == null) { - super.setResourcesRegistry(new SWTResourcesRegistry(display)); - } - return super.getResourcesRegistry(); - } - - @Override - public Element getElement(Object element) { - if (element instanceof CSSStylableElement - && ((CSSStylableElement) element).getNativeWidget() instanceof Widget) { - return (CSSStylableElement) element; - } else if (element instanceof Widget) { - if (isStylable((Widget) element)) { - return super.getElement(element); - } - } else { - // FIXME: we need to pass through the ThemeElementDefinitions; - // perhaps they should be handled by a separate engine - return super.getElement(element); - } - return null; - } - - /** - * Return true if the given widget can be styled - * - * @param widget - * the widget - * @return true if the widget can be styled - */ - protected boolean isStylable(Widget widget) { - // allows widgets to be selectively excluded from styling - return !widget.isDisposed() - && !Boolean.TRUE.equals(widget.getData("org.eclipse.e4.ui.css.disabled")); //$NON-NLS-1$ - } - - @Override - public void reset() { - for (CSSElementContext elementContext : getElementsContext().values()) { - Element element = elementContext.getElement(); - if (element instanceof WidgetElement - && isApplicableToReset((WidgetElement) element)) { - ((WidgetElement) element).reset(); - } - } - - getResourcesRegistry().dispose(); - super.reset(); - } - - private boolean isApplicableToReset(WidgetElement element) { - if (element.getNativeWidget() instanceof Widget) { - return !((Widget) element.getNativeWidget()).isDisposed(); - } - return false; - } - -} diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/engine/CSSSWTEngineImpl.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/engine/CSSSWTEngineImpl.java index c406a848ea6..8bfc22e01c4 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/engine/CSSSWTEngineImpl.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/engine/CSSSWTEngineImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2008, 2019 Angelo Zerr and others. + * Copyright (c) 2008, 2026 Angelo Zerr and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -16,34 +16,63 @@ import org.eclipse.core.runtime.ILog; import org.eclipse.core.runtime.RegistryFactory; +import org.eclipse.e4.ui.css.core.dom.CSSStylableElement; +import org.eclipse.e4.ui.css.core.engine.CSSElementContext; +import org.eclipse.e4.ui.css.core.impl.engine.CSSEngineImpl; import org.eclipse.e4.ui.css.core.impl.engine.RegistryCSSElementProvider; import org.eclipse.e4.ui.css.core.impl.engine.RegistryCSSPropertyHandlerProvider; +import org.eclipse.e4.ui.css.core.resources.IResourcesRegistry; +import org.eclipse.e4.ui.css.swt.dom.WidgetElement; +import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTColorConverterImpl; +import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTCursorConverterImpl; +import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTFontConverterImpl; +import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTFontDataConverterImpl; +import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTGradientConverterImpl; +import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTImageConverterImpl; +import org.eclipse.e4.ui.css.swt.properties.converters.CSSValueSWTRGBConverterImpl; +import org.eclipse.e4.ui.css.swt.resources.SWTResourceRegistryKeyFactory; +import org.eclipse.e4.ui.css.swt.resources.SWTResourcesRegistry; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Widget; +import org.w3c.dom.Element; /** - * CSS SWT Engine implementation which configure CSSEngineImpl to apply styles - * to SWT widgets with static handler strategy. + * CSS SWT Engine. Configures {@link CSSEngineImpl} with the SWT-specific + * value converters and the registry-driven element + property handler + * providers, and applies styles to SWT widgets. */ -public class CSSSWTEngineImpl extends AbstractCSSSWTEngineImpl { +public class CSSSWTEngineImpl extends CSSEngineImpl { - private DisposeListener disposeListener; + protected Display display; + + private final DisposeListener disposeListener = e -> handleWidgetDisposed(e.widget); public CSSSWTEngineImpl(Display display) { - super(display); - init(); + this(display, false); } public CSSSWTEngineImpl(Display display, boolean lazyApplyingStyles) { - super(display, lazyApplyingStyles); - init(); - } + this.display = display; - private void init() { - disposeListener = e -> handleWidgetDisposed(e.widget); + registerCSSValueConverter(CSSValueSWTRGBConverterImpl.INSTANCE); + registerCSSValueConverter(CSSValueSWTColorConverterImpl.INSTANCE); + registerCSSValueConverter(CSSValueSWTGradientConverterImpl.INSTANCE); + registerCSSValueConverter(CSSValueSWTCursorConverterImpl.INSTANCE); + registerCSSValueConverter(CSSValueSWTFontConverterImpl.INSTANCE); + registerCSSValueConverter(CSSValueSWTFontDataConverterImpl.INSTANCE); + registerCSSValueConverter(CSSValueSWTImageConverterImpl.INSTANCE); + + if (lazyApplyingStyles) { + new CSSSWTApplyStylesListener(display, this); + } + + setElementProvider(new RegistryCSSElementProvider(RegistryFactory.getRegistry())); + propertyHandlerProviders.add(new RegistryCSSPropertyHandlerProvider(RegistryFactory.getRegistry())); + + setResourceRegistryKeyFactory(new SWTResourceRegistryKeyFactory()); } @Override @@ -54,13 +83,58 @@ protected void hookNativeWidget(Object widget) { } @Override - protected void initializeCSSPropertyHandlers() { - propertyHandlerProviders.add(new RegistryCSSPropertyHandlerProvider(RegistryFactory.getRegistry())); + public IResourcesRegistry getResourcesRegistry() { + IResourcesRegistry resourcesRegistry = super.getResourcesRegistry(); + if (resourcesRegistry == null) { + super.setResourcesRegistry(new SWTResourcesRegistry(display)); + } + return super.getResourcesRegistry(); } @Override - protected void initializeCSSElementProvider() { - setElementProvider(new RegistryCSSElementProvider(RegistryFactory.getRegistry())); + public Element getElement(Object element) { + if (element instanceof CSSStylableElement + && ((CSSStylableElement) element).getNativeWidget() instanceof Widget) { + return (CSSStylableElement) element; + } else if (element instanceof Widget) { + if (isStylable((Widget) element)) { + return super.getElement(element); + } + } else { + // FIXME: we need to pass through the ThemeElementDefinitions; + // perhaps they should be handled by a separate engine + return super.getElement(element); + } + return null; + } + + /** + * Return true if the given widget can be styled. + */ + protected boolean isStylable(Widget widget) { + return !widget.isDisposed() + && !Boolean.TRUE.equals(widget.getData("org.eclipse.e4.ui.css.disabled")); //$NON-NLS-1$ + } + + @Override + public void reset() { + for (CSSElementContext elementContext : getElementsContext().values()) { + Element element = elementContext.getElement(); + if (element instanceof WidgetElement + && isApplicableToReset((WidgetElement) element)) { + ((WidgetElement) element).reset(); + } + } + + getResourcesRegistry().dispose(); + super.reset(); + } + + private boolean isApplicableToReset(WidgetElement element) { + if (element.getNativeWidget() instanceof Widget) { + return !((Widget) element.getNativeWidget()).isDisposed(); + } + return false; } @Override @@ -78,5 +152,4 @@ public void reapply() { } } } - } diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/PropertyHelper.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/PropertyHelper.java deleted file mode 100644 index b018a6beb84..00000000000 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/PropertyHelper.java +++ /dev/null @@ -1,80 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2010, 2015 Tom Schindl 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: - * Tom Schindl - initial API and implementation - *******************************************************************************/ -package org.eclipse.e4.ui.css.swt.helpers; - -import java.util.HashMap; - -import java.lang.reflect.Method; - -import java.util.Map; - -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; - -public class PropertyHelper { - private static final Map NOTNESTEDCACHE = new HashMap<>(); - - public static Object getProperty(Object bean, String attr) - throws Exception { - String key = bean.getClass().getName() + "#" + attr; - - if (attr.indexOf('.') == -1) { - Method readMethod = NOTNESTEDCACHE.get(key); - if (readMethod != null) { - return readMethod.invoke(bean); - } - } - - Method readMethod = null; - Object value = bean; - for (String part : attr.split("\\.")) { - PropertyDescriptor desc = getPropertyDescriptor(value.getClass(), - part); - if (desc != null) { - readMethod = desc.getReadMethod(); - } - - if (readMethod == null) { - throw new IllegalArgumentException("Attribute '" + part - + "' is not known in '" + value + "'"); - } else { - value = readMethod.invoke(value); - } - } - - if (attr.indexOf('.') == -1) { - NOTNESTEDCACHE.put(key,readMethod); - } - - return value; - } - - private static PropertyDescriptor getPropertyDescriptor(Class clazz, - String name) throws IntrospectionException { - PropertyDescriptor[] descs = getPropertyDescriptor(clazz); - for (PropertyDescriptor desc : descs) { - if (desc.getName().equals(name)) { - return desc; - } - } - return null; - } - - private static PropertyDescriptor[] getPropertyDescriptor(Class clazz) - throws IntrospectionException { - return Introspector.getBeanInfo(clazz).getPropertyDescriptors(); - } - -} \ No newline at end of file diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/URI.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/URI.java deleted file mode 100644 index 44ca2d533bf..00000000000 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/helpers/URI.java +++ /dev/null @@ -1,3205 +0,0 @@ -/** - * Copyright (c) 2002, 2015 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 - Initial API and implementation - */ -package org.eclipse.e4.ui.css.swt.helpers; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.StringTokenizer; - -/** - * A representation of a Uniform Resource Identifier (URI), as specified by - * RFC 2396, with certain - * enhancements. A URI instance can be created by specifying - * values for its components, or by providing a single URI string, which is - * parsed into its components. Static factory methods whose names begin - * with "create" are used for both forms of object creation. No public or - * protected constructors are provided; this class can not be subclassed. - * - *

Like String, URI is an immutable class; - * a URI instance offers several by-value methods that return a - * new URI object based on its current state. Most useful, - * a relative URI can be {@link #resolve(URI) resolve}d against - * a base absolute URI -- the latter typically identifies the - * document in which the former appears. The inverse to this is {@link - * #deresolve(URI) deresolve}, which answers the question, "what relative - * URI will resolve, against the given base, to this absolute URI?" - * - *

In the RFC, much - * attention is focused on a hierarchical naming system used widely to - * locate resources via common protocols such as HTTP, FTP, and Gopher, and - * to identify files on a local file system. Accordingly, most of this - * class's functionality is for handling such URIs, which can be identified - * via {@link #isHierarchical isHierarchical}. - * - *

- * The primary enhancement beyond the RFC description is an optional - * device component. Instead of treating the device as just another segment - * in the path, it can be stored as a separate component (almost a - * sub-authority), with the root below it. For example, resolving - * /bar against file:///c:/foo would result in - * file:///c:/bar being returned. Also, you cannot take - * the parent of a device, so resolving .. against - * file:///c:/ would not yield file:///, as you - * might expect. This feature is useful when working with file-scheme - * URIs, as devices do not typically occur in protocol-based ones. A - * device-enabled URI is created by parsing a string with - * {@link #createURI(String) createURI}; if the first segment of the path - * ends with the : character, it is stored (including the colon) - * as the device, instead. Alternately, either the {@link - * #createHierarchicalURI(String, String, String, String, String) no-path} - * or the {@link #createHierarchicalURI(String, String, String, String[], - * String, String) absolute-path} form of createHierarchicalURI() - * can be used, in which a non-null device parameter can be - * specified. - * - *

- * The other enhancement provides support for the almost-hierarchical - * form used for files within archives, such as the JAR scheme, defined - * for the Java Platform in the documentation for {@link - * java.net.JarURLConnection}. By default, this support is enabled for - * absolute URIs with scheme equal to "jar", "zip", or "archive" (ignoring case), and - * is implemented by a hierarchical URI, whose authority includes the - * entire URI of the archive, up to and including the ! - * character. The URI of the archive must have no fragment. The whole - * archive URI must have no device and an absolute path. Special handling - * is supported for {@link #createURI(String) creating}, {@link - * #validArchiveAuthority validating}, {@link #devicePath getting the path} - * from, and {@link #toString() displaying} archive URIs. In all other - * operations, including {@link #resolve(URI) resolving} and {@link - * #deresolve(URI) deresolving}, they are handled like any ordinary URI. - * The schemes that identify archive URIs can be changed from their default - * by setting the org.eclipse.emf.common.util.URI.archiveSchemes - * system property. Multiple schemes should be space separated, and the test - * of whether a URI's scheme matches is always case-insensitive. - * - *

This implementation does not impose all of the restrictions on - * character validity that are specified in the RFC. Static methods whose - * names begin with "valid" are used to test whether a given string is valid - * value for the various URI components. Presently, these tests place no - * restrictions beyond what would have been required in order for {@link - * #createURI(String) createURI} to have parsed them correctly from a single - * URI string. If necessary in the future, these tests may be made more - * strict, to better conform to the RFC. - * - *

Another group of static methods, whose names begin with "encode", use - * percent escaping to encode any characters that are not permitted in the - * various URI components. Another static method is provided to {@link - * #decode decode} encoded strings. An escaped character is represented as - * a percent symbol (%), followed by two hex digits that specify - * the character code. These encoding methods are more strict than the - * validation methods described above. They ensure validity according to the - * RFC, with one exception: non-ASCII characters. - * - *

The RFC allows only characters that can be mapped to 7-bit US-ASCII - * representations. Non-ASCII, single-byte characters can be used only via - * percent escaping, as described above. This implementation uses Java's - * Unicode char and String representations, and - * makes no attempt to encode characters 0xA0 and above. Characters in the - * range 0x80-0x9F are still escaped. In this respect, EMF's notion of a URI - * is actually more like an IRI (Internationalized Resource Identifier), for - * which an RFC is now in draft - * form. - * - *

Finally, note the difference between a null parameter to - * the static factory methods and an empty string. The former signifies the - * absence of a given URI component, while the latter simply makes the - * component blank. This can have a significant effect when resolving. For - * example, consider the following two URIs: /bar (with no - * authority) and ///bar (with a blank authority). Imagine - * resolving them against a base with an authority, such as - * http://www.eclipse.org/. The former case will yield - * http://www.eclipse.org/bar, as the base authority will be - * preserved. In the latter case, the empty authority will override the - * base authority, resulting in http:///bar! - */ -public final class URI -{ - // Common to all URI types. - private final int hashCode; - private static final int HIERARICHICAL_FLAG = 0x0100; - private final String scheme; // null -> relative URI reference - private final String authority; - private final String fragment; - private URI cachedTrimFragment; - private String cachedToString; - //private final boolean iri; - //private URI cachedASCIIURI; - - // Applicable only to a hierarchical URI. - private final String device; - private static final int ABSOLUTE_PATH_FLAG = 0x0010; - private final String[] segments; // empty last segment -> trailing separator - private final String query; - - // A cache of URIs, keyed by the strings from which they were created. - // The fragment of any URI is removed before caching it here, to minimize - // the size of the cache in the usual case where most URIs only differ by - // the fragment. - private static final URICache uriCache = new URICache(); - - private static class URICache extends HashMap> - { - private static final long serialVersionUID = 1L; - - static final int MIN_LIMIT = 1000; - int count; - int limit = MIN_LIMIT; - - public synchronized URI get(String key) - { - WeakReference reference = super.get(key); - return reference == null ? null : reference.get(); - } - - public synchronized void put(String key, URI value) - { - super.put(key, new WeakReference<>(value)); - if (++count > limit) - { - cleanGCedValues(); - } - } - - private void cleanGCedValues() - { - for (Iterator>> i = entrySet().iterator(); i.hasNext(); ) - { - Map.Entry> entry = i.next(); - WeakReference reference = entry.getValue(); - if (reference.get() == null) - { - i.remove(); - } - } - count = 0; - limit = Math.max(MIN_LIMIT, size() / 2); - } - } - - // The lower-cased schemes that will be used to identify archive URIs. - private static final Set archiveSchemes; - - // Identifies a file-type absolute URI. - private static final String SCHEME_FILE = "file"; - private static final String SCHEME_JAR = "jar"; - private static final String SCHEME_ZIP = "zip"; - private static final String SCHEME_ARCHIVE = "archive"; - private static final String SCHEME_PLATFORM = "platform"; - - // Special segment values interpreted at resolve and resolve time. - private static final String SEGMENT_EMPTY = ""; - private static final String SEGMENT_SELF = "."; - private static final String SEGMENT_PARENT = ".."; - private static final String[] NO_SEGMENTS = new String[0]; - - // Separators for parsing a URI string. - private static final char SCHEME_SEPARATOR = ':'; - private static final String AUTHORITY_SEPARATOR = "//"; - private static final char DEVICE_IDENTIFIER = ':'; - private static final char SEGMENT_SEPARATOR = '/'; - private static final char QUERY_SEPARATOR = '?'; - private static final char FRAGMENT_SEPARATOR = '#'; - private static final char USER_INFO_SEPARATOR = '@'; - private static final char PORT_SEPARATOR = ':'; - private static final char FILE_EXTENSION_SEPARATOR = '.'; - private static final char ARCHIVE_IDENTIFIER = '!'; - private static final String ARCHIVE_SEPARATOR = "!/"; - - // Characters to use in escaping. - private static final char ESCAPE = '%'; - private static final char[] HEX_DIGITS = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - - // Some character classes, as defined in RFC 2396's BNF for URI. - // These are 128-bit bitmasks, stored as two longs, where the Nth bit is set - // iff the ASCII character with value N is included in the set. These are - // created with the highBitmask() and lowBitmask() methods defined below, - // and a character is tested against them using matches(). - // - private static final long ALPHA_HI = highBitmask('a', 'z') | highBitmask('A', 'Z'); - private static final long ALPHA_LO = lowBitmask('a', 'z') | lowBitmask('A', 'Z'); - private static final long DIGIT_HI = highBitmask('0', '9'); - private static final long DIGIT_LO = lowBitmask('0', '9'); - private static final long ALPHANUM_HI = ALPHA_HI | DIGIT_HI; - private static final long ALPHANUM_LO = ALPHA_LO | DIGIT_LO; - private static final long HEX_HI = DIGIT_HI | highBitmask('A', 'F') | highBitmask('a', 'f'); - private static final long HEX_LO = DIGIT_LO | lowBitmask('A', 'F') | lowBitmask('a', 'f'); - private static final long UNRESERVED_HI = ALPHANUM_HI | highBitmask("-_.!~*'()"); - private static final long UNRESERVED_LO = ALPHANUM_LO | lowBitmask("-_.!~*'()"); - private static final long RESERVED_HI = highBitmask(";/?:@&=+$,"); - private static final long RESERVED_LO = lowBitmask(";/?:@&=+$,"); - private static final long URIC_HI = RESERVED_HI | UNRESERVED_HI; // | ucschar | escaped - private static final long URIC_LO = RESERVED_LO | UNRESERVED_LO; - - // Additional useful character classes, including characters valid in certain - // URI components and separators used in parsing them out of a string. - // - private static final long SEGMENT_CHAR_HI = UNRESERVED_HI | highBitmask(";:@&=+$,"); // | ucschar | escaped - private static final long SEGMENT_CHAR_LO = UNRESERVED_LO | lowBitmask(";:@&=+$,"); - private static final long PATH_CHAR_HI = SEGMENT_CHAR_HI | highBitmask('/'); // | ucschar | escaped - private static final long PATH_CHAR_LO = SEGMENT_CHAR_LO | lowBitmask('/'); -// private static final long SCHEME_CHAR_HI = ALPHANUM_HI | highBitmask("+-."); -// private static final long SCHEME_CHAR_LO = ALPHANUM_LO | lowBitmask("+-."); - private static final long MAJOR_SEPARATOR_HI = highBitmask(":/?#"); - private static final long MAJOR_SEPARATOR_LO = lowBitmask(":/?#"); - private static final long SEGMENT_END_HI = highBitmask("/?#"); - private static final long SEGMENT_END_LO = lowBitmask("/?#"); - - // The intent of this was to switch over to encoding platform resource URIs - // by default, but allow people to use a system property to avoid this. - // However, that caused problems for people and we had to go back to not - // encoding and introduce yet another factory method that explicitly enables - // encoding. - // - private static final boolean ENCODE_PLATFORM_RESOURCE_URIS = - System.getProperty("org.eclipse.emf.common.util.URI.encodePlatformResourceURIs") != null && - !"false".equalsIgnoreCase(System.getProperty("org.eclipse.emf.common.util.URI.encodePlatformResourceURIs")); - - // Static initializer for archiveSchemes. - static - { - Set set = new HashSet<>(); - String propertyValue = System.getProperty("org.eclipse.emf.common.util.URI.archiveSchemes"); - - if (propertyValue == null) - { - set.add(SCHEME_JAR); - set.add(SCHEME_ZIP); - set.add(SCHEME_ARCHIVE); - } - else - { - for (StringTokenizer t = new StringTokenizer(propertyValue); t.hasMoreTokens(); ) - { - set.add(t.nextToken().toLowerCase()); - } - } - - archiveSchemes = Collections.unmodifiableSet(set); - } - - // Returns the lower half bitmask for the given ASCII character. - private static long lowBitmask(char c) - { - return c < 64 ? 1L << c : 0L; - } - - // Returns the upper half bitmask for the given ACSII character. - private static long highBitmask(char c) - { - return c >= 64 && c < 128 ? 1L << (c - 64) : 0L; - } - - // Returns the lower half bitmask for all ASCII characters between the two - // given characters, inclusive. - private static long lowBitmask(char from, char to) - { - long result = 0L; - if (from < 64 && from <= to) - { - to = to < 64 ? to : 63; - for (char c = from; c <= to; c++) - { - result |= (1L << c); - } - } - return result; - } - - // Returns the upper half bitmask for all AsCII characters between the two - // given characters, inclusive. - private static long highBitmask(char from, char to) - { - return to < 64 ? 0 : lowBitmask((char)(from < 64 ? 0 : from - 64), (char)(to - 64)); - } - - // Returns the lower half bitmask for all the ASCII characters in the given - // string. - private static long lowBitmask(String chars) - { - long result = 0L; - for (int i = 0, len = chars.length(); i < len; i++) - { - char c = chars.charAt(i); - if (c < 64) { - result |= (1L << c); - } - } - return result; - } - - // Returns the upper half bitmask for all the ASCII characters in the given - // string. - private static long highBitmask(String chars) - { - long result = 0L; - for (int i = 0, len = chars.length(); i < len; i++) - { - char c = chars.charAt(i); - if (c >= 64 && c < 128) { - result |= (1L << (c - 64)); - } - } - return result; - } - - // Returns whether the given character is in the set specified by the given - // bitmask. - private static boolean matches(char c, long highBitmask, long lowBitmask) - { - if (c >= 128) { - return false; - } - return c < 64 ? - ((1L << c) & lowBitmask) != 0 : - ((1L << (c - 64)) & highBitmask) != 0; - } - - // Debugging method: converts the given long to a string of binary digits. -/* - private static String toBits(long l) - { - StringBuilder result = new StringBuilder(); - for (int i = 0; i < 64; i++) - { - boolean b = (l & 1L) != 0; - result.insert(0, b ? '1' : '0'); - l >>= 1; - } - return result.toString(); - } -*/ - - /** - * Static factory method for a generic, non-hierarchical URI. There is no - * concept of a relative non-hierarchical URI; such an object cannot be - * created. - * - * @exception java.lang.IllegalArgumentException if scheme is - * null, if scheme is an archive - * URI scheme, or if scheme, opaquePart, or - * fragment is not valid according to {@link #validScheme - * validScheme}, {@link #validOpaquePart validOpaquePart}, or {@link - * #validFragment validFragment}, respectively. - */ - public static URI createGenericURI(String scheme, String opaquePart, - String fragment) - { - if (scheme == null) - { - throw new IllegalArgumentException("relative non-hierarchical URI"); - } - - if (isArchiveScheme(scheme)) - { - throw new IllegalArgumentException("non-hierarchical archive URI"); - } - - validateURI(false, scheme, opaquePart, null, NO_SEGMENTS, null, fragment); - return new URI(false, scheme, opaquePart, null, false, NO_SEGMENTS, null, fragment); - } - - /** - * Static factory method for a hierarchical URI with no path. The - * URI will be relative if scheme is non-null, and absolute - * otherwise. An absolute URI with no path requires a non-null - * authority and/or device. - * - * @exception java.lang.IllegalArgumentException if scheme is - * non-null while authority and device are null, - * if scheme is an archive - * URI scheme, or if scheme, authority, - * device, query, or fragment is not - * valid according to {@link #validScheme validSheme}, {@link - * #validAuthority validAuthority}, {@link #validDevice validDevice}, - * {@link #validQuery validQuery}, or {@link #validFragment validFragment}, - * respectively. - */ - public static URI createHierarchicalURI(String scheme, String authority, - String device, String query, - String fragment) - { - if (scheme != null && authority == null && device == null) - { - throw new IllegalArgumentException( - "absolute hierarchical URI without authority, device, path"); - } - - if (isArchiveScheme(scheme)) - { - throw new IllegalArgumentException("archive URI with no path"); - } - - validateURI(true, scheme, authority, device, NO_SEGMENTS, query, fragment); - return new URI(true, scheme, authority, device, false, NO_SEGMENTS, query, fragment); - } - - /** - * Static factory method for a hierarchical URI with absolute path. - * The URI will be relative if scheme is non-null, and - * absolute otherwise. - * - * @param segments an array of non-null strings, each representing one - * segment of the path. As an absolute path, it is automatically - * preceded by a / separator. If desired, a trailing - * separator should be represented by an empty-string segment as the last - * element of the array. - * - * @exception java.lang.IllegalArgumentException if scheme is - * an archive URI scheme and - * device is non-null, or if scheme, - * authority, device, segments, - * query, or fragment is not valid according to - * {@link #validScheme validScheme}, {@link #validAuthority validAuthority} - * or {@link #validArchiveAuthority validArchiveAuthority}, {@link - * #validDevice validDevice}, {@link #validSegments validSegments}, {@link - * #validQuery validQuery}, or {@link #validFragment validFragment}, as - * appropriate. - */ - public static URI createHierarchicalURI(String scheme, String authority, - String device, String[] segments, - String query, String fragment) - { - if (isArchiveScheme(scheme) && device != null) - { - throw new IllegalArgumentException("archive URI with device"); - } - - segments = fix(segments); - validateURI(true, scheme, authority, device, segments, query, fragment); - return new URI(true, scheme, authority, device, true, segments, query, fragment); - } - - /** - * Static factory method for a relative hierarchical URI with relative - * path. - * - * @param segments an array of non-null strings, each representing one - * segment of the path. A trailing separator is represented by an - * empty-string segment at the end of the array. - * - * @exception java.lang.IllegalArgumentException if segments, - * query, or fragment is not valid according to - * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or - * {@link #validFragment validFragment}, respectively. - */ - public static URI createHierarchicalURI(String[] segments, String query, - String fragment) - { - segments = fix(segments); - validateURI(true, null, null, null, segments, query, fragment); - return new URI(true, null, null, null, false, segments, query, fragment); - } - - // Converts null to length-zero array, and clones array to ensure - // immutability. - private static String[] fix(String[] segments) - { - return segments == null ? NO_SEGMENTS : segments.clone(); - } - - /** - * Static factory method based on parsing a URI string, with - * explicit device support and handling - * for archive URIs enabled. The - * specified string is parsed as described in RFC 2396, and an - * appropriate URI is created and returned. Note that - * validity testing is not as strict as in the RFC; essentially, only - * separator characters are considered. This method also does not perform - * encoding of invalid characters, so it should only be used when the URI - * string is known to have already been encoded, so as to avoid double - * encoding. - * - * @exception java.lang.IllegalArgumentException if any component parsed - * from uri is not valid according to {@link #validScheme - * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link - * #validAuthority validAuthority}, {@link #validArchiveAuthority - * validArchiveAuthority}, {@link #validDevice validDevice}, {@link - * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link - * #validFragment validFragment}, as appropriate. - */ - public static URI createURI(String uri) - { - return createURIWithCache(uri); - } - - /** - * Static factory method that encodes and parses the given URI string. - * Appropriate encoding is performed for each component of the URI. - * If more than one # is in the string, the last one is - * assumed to be the fragment's separator, and any others are encoded. - * This method is the simplest way to safely parse an arbitrary URI string. - * - * @param ignoreEscaped true to leave % characters - * unescaped if they already begin a valid three-character escape sequence; - * false to encode all % characters. This - * capability is provided to allow partially encoded URIs to be "fixed", - * while avoiding adding double encoding; however, it is usual just to - * specify false to perform ordinary encoding. - * - * @exception java.lang.IllegalArgumentException if any component parsed - * from uri is not valid according to {@link #validScheme - * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link - * #validAuthority validAuthority}, {@link #validArchiveAuthority - * validArchiveAuthority}, {@link #validDevice validDevice}, {@link - * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link - * #validFragment validFragment}, as appropriate. - */ - public static URI createURI(String uri, boolean ignoreEscaped) - { - return createURIWithCache(encodeURI(uri, ignoreEscaped, FRAGMENT_LAST_SEPARATOR)); - } - - /** - * When specified as the last argument to {@link #createURI(String, boolean, int) - * createURI}, indicates that there is no fragment, so any # characters - * should be encoded. - * @see #createURI(String, boolean, int) - */ - public static final int FRAGMENT_NONE = 0; - - /** - * When specified as the last argument to {@link #createURI(String, boolean, int) - * createURI}, indicates that the first # character should be taken as - * the fragment separator, and any others should be encoded. - * @see #createURI(String, boolean, int) - */ - public static final int FRAGMENT_FIRST_SEPARATOR = 1; - - /** - * When specified as the last argument to {@link #createURI(String, boolean, int) - * createURI}, indicates that the last # character should be taken as - * the fragment separator, and any others should be encoded. - * @see #createURI(String, boolean, int) - */ - public static final int FRAGMENT_LAST_SEPARATOR = 2; - - /** - * Static factory method that encodes and parses the given URI string. - * Appropriate encoding is performed for each component of the URI. - * Control is provided over which, if any, # should be - * taken as the fragment separator and which should be encoded. - * This method is the preferred way to safely parse an arbitrary URI string - * that is known to contain # characters in the fragment or to - * have no fragment at all. - * - * @param ignoreEscaped true to leave % characters - * unescaped if they already begin a valid three-character escape sequence; - * false to encode all % characters. This - * capability is provided to allow partially encoded URIs to be "fixed", - * while avoiding adding double encoding; however, it is usual just to - * specify false to perform ordinary encoding. - * - * @param fragmentLocationStyle one of {@link #FRAGMENT_NONE}, - * {@link #FRAGMENT_FIRST_SEPARATOR}, or {@link #FRAGMENT_LAST_SEPARATOR}, - * indicating which, if any, of the # characters should be - * considered the fragment separator. Any others will be encoded. - * - * @exception java.lang.IllegalArgumentException if any component parsed - * from uri is not valid according to {@link #validScheme - * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link - * #validAuthority validAuthority}, {@link #validArchiveAuthority - * validArchiveAuthority}, {@link #validDevice validDevice}, {@link - * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link - * #validFragment validFragment}, as appropriate. - */ - public static URI createURI(String uri, boolean ignoreEscaped, int fragmentLocationStyle) - { - return createURIWithCache(encodeURI(uri, ignoreEscaped, fragmentLocationStyle)); - } - - /** - * Static factory method based on parsing a URI string, with - * explicit device support enabled. - * Note that validity testing is not a strict as in the RFC; essentially, - * only separator characters are considered. So, for example, non-Latin - * alphabet characters appearing in the scheme would not be considered an - * error. - * - * @exception java.lang.IllegalArgumentException if any component parsed - * from uri is not valid according to {@link #validScheme - * validScheme}, {@link #validOpaquePart validOpaquePart}, {@link - * #validAuthority validAuthority}, {@link #validArchiveAuthority - * validArchiveAuthority}, {@link #validDevice validDevice}, {@link - * #validSegments validSegments}, {@link #validQuery validQuery}, or {@link - * #validFragment validFragment}, as appropriate. - * - * @deprecated Use {@link #createURI(String) createURI}, which now has explicit - * device support enabled. The two methods now operate identically. - */ - @Deprecated - public static URI createDeviceURI(String uri) - { - return createURIWithCache(uri); - } - - // Uses a cache to speed up creation of a URI from a string. The cache - // is consulted to see if the URI, less any fragment, has already been - // created. If needed, the fragment is re-appended to the cached URI, - // which is considerably more efficient than creating the whole URI from - // scratch. If the URI wasn't found in the cache, it is created using - // parseIntoURI() and then cached. This method should always be used - // by string-parsing factory methods, instead of parseIntoURI() directly. - /** - * This method was included in the public API by mistake. - * - * @deprecated Please use {@link #createURI(String) createURI} instead. - */ - @Deprecated - public static URI createURIWithCache(String uri) - { - int i = uri.indexOf(FRAGMENT_SEPARATOR); - String base = i == -1 ? uri : uri.substring(0, i); - String fragment = i == -1 ? null : uri.substring(i + 1); - - URI result = uriCache.get(base); - - if (result == null) - { - result = parseIntoURI(base); - uriCache.put(base, result); - } - - if (fragment != null) - { - result = result.appendFragment(fragment); - } - return result; - } - - // String-parsing implementation. - private static URI parseIntoURI(String uri) - { - boolean hierarchical = true; - String scheme = null; - String authority = null; - String device = null; - boolean absolutePath = false; - String[] segments = NO_SEGMENTS; - String query = null; - String fragment = null; - - int i = 0; - int j = find(uri, i, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO); - - if (j < uri.length() && uri.charAt(j) == SCHEME_SEPARATOR) - { - scheme = uri.substring(i, j); - i = j + 1; - } - - boolean archiveScheme = isArchiveScheme(scheme); - if (archiveScheme) - { - j = uri.lastIndexOf(ARCHIVE_SEPARATOR); - if (j == -1) - { - throw new IllegalArgumentException("no archive separator"); - } - hierarchical = true; - authority = uri.substring(i, ++j); - i = j; - } - else if (uri.startsWith(AUTHORITY_SEPARATOR, i)) - { - i += AUTHORITY_SEPARATOR.length(); - j = find(uri, i, SEGMENT_END_HI, SEGMENT_END_LO); - authority = uri.substring(i, j); - i = j; - } - else if (scheme != null && - (i == uri.length() || uri.charAt(i) != SEGMENT_SEPARATOR)) - { - hierarchical = false; - j = uri.indexOf(FRAGMENT_SEPARATOR, i); - if (j == -1) { - j = uri.length(); - } - authority = uri.substring(i, j); - i = j; - } - - if (!archiveScheme && i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR) - { - j = find(uri, i + 1, SEGMENT_END_HI, SEGMENT_END_LO); - String s = uri.substring(i + 1, j); - - if (s.length() > 0 && s.charAt(s.length() - 1) == DEVICE_IDENTIFIER) - { - device = s; - i = j; - } - } - - if (i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR) - { - i++; - absolutePath = true; - } - - if (segmentsRemain(uri, i)) - { - List segmentList = new ArrayList<>(); - - while (segmentsRemain(uri, i)) - { - j = find(uri, i, SEGMENT_END_HI, SEGMENT_END_LO); - segmentList.add(uri.substring(i, j)); - i = j; - - if (i < uri.length() && uri.charAt(i) == SEGMENT_SEPARATOR) - { - if (!segmentsRemain(uri, ++i)) { - segmentList.add(SEGMENT_EMPTY); - } - } - } - segments = new String[segmentList.size()]; - segmentList.toArray(segments); - } - - if (i < uri.length() && uri.charAt(i) == QUERY_SEPARATOR) - { - j = uri.indexOf(FRAGMENT_SEPARATOR, ++i); - if (j == -1) { - j = uri.length(); - } - query = uri.substring(i, j); - i = j; - } - - if (i < uri.length()) // && uri.charAt(i) == FRAGMENT_SEPARATOR (implied) - { - fragment = uri.substring(++i); - } - - validateURI(hierarchical, scheme, authority, device, segments, query, fragment); - return new URI(hierarchical, scheme, authority, device, absolutePath, segments, query, fragment); - } - - // Checks whether the string contains any more segments after the one that - // starts at position i. - private static boolean segmentsRemain(String uri, int i) - { - return i < uri.length() && uri.charAt(i) != QUERY_SEPARATOR && - uri.charAt(i) != FRAGMENT_SEPARATOR; - } - - // Finds the next occurrence of one of the characters in the set represented - // by the given bitmask in the given string, beginning at index i. The index - // of the first found character, or s.length() if there is none, is - // returned. Before searching, i is limited to the range [0, s.length()]. - // - private static int find(String s, int i, long highBitmask, long lowBitmask) - { - int len = s.length(); - if (i >= len) { - return len; - } - - for (i = i > 0 ? i : 0; i < len; i++) - { - if (matches(s.charAt(i), highBitmask, lowBitmask)) { - break; - } - } - return i; - } - - /** - * Static factory method based on parsing a {@link java.io.File} path - * string. The pathName is converted into an appropriate - * form, as follows: platform specific path separators are converted to - * /; the path is encoded; and a "file" scheme and, if missing, - * a leading /, are added to an absolute path. The result - * is then parsed using {@link #createURI(String) createURI}. - * - *

The encoding step escapes all spaces, # characters, and - * other characters disallowed in URIs, as well as ?, which - * would delimit a path from a query. Decoding is automatically performed - * by {@link #toFileString toFileString}, and can be applied to the values - * returned by other accessors by via the static {@link #decode(String) - * decode} method. - * - *

A relative path with a specified device (something like - * C:myfile.txt) cannot be expressed as a valid URI. - * - * @exception java.lang.IllegalArgumentException if pathName - * specifies a device and a relative path, or if any component of the path - * is not valid according to {@link #validAuthority validAuthority}, {@link - * #validDevice validDevice}, or {@link #validSegments validSegments}, - * {@link #validQuery validQuery}, or {@link #validFragment validFragment}. - */ - public static URI createFileURI(String pathName) - { - File file = new File(pathName); - String uri = File.separatorChar != '/' ? pathName.replace(File.separatorChar, SEGMENT_SEPARATOR) : pathName; - uri = encode(uri, PATH_CHAR_HI, PATH_CHAR_LO, false); - if (file.isAbsolute()) - { - return createURI((uri.charAt(0) == SEGMENT_SEPARATOR ? "file:" : "file:/") + uri); - } - else - { - URI result = createURI(uri); - if (result.scheme() != null) - { - throw new IllegalArgumentException("invalid relative pathName: " + pathName); - } - return result; - } - } - - /** - * Static factory method based on parsing a workspace-relative path string. - * - *

The pathName must be of the form: - *

-	 *   /project-name/path
- * - *

Platform-specific path separators will be converted to slashes. - * If not included, the leading path separator will be added. The - * result will be of this form, which is parsed using {@link #createURI(String) - * createURI}: - *

-	 *   platform:/resource/project-name/path
- * - *

This scheme supports relocatable projects in Eclipse and in - * stand-alone EMF. - * - *

Path encoding is performed only if the - * org.eclipse.emf.common.util.URI.encodePlatformResourceURIs - * system property is set to "true". Decoding can be performed with the - * static {@link #decode(String) decode} method. - * - * @exception java.lang.IllegalArgumentException if any component parsed - * from the path is not valid according to {@link #validDevice validDevice}, - * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or - * {@link #validFragment validFragment}. - * - * @see org.eclipse.core.runtime.Platform#resolve - * @see #createPlatformResourceURI(String, boolean) - * @deprecated Use {@link #createPlatformResourceURI(String, boolean)} instead. - */ - @Deprecated - public static URI createPlatformResourceURI(String pathName) - { - return createPlatformResourceURI(pathName, ENCODE_PLATFORM_RESOURCE_URIS); - } - - /** - * Static factory method based on parsing a workspace-relative path string, - * with an option to encode the created URI. - * - *

The pathName must be of the form: - *

-	 *   /project-name/path
- * - *

Platform-specific path separators will be converted to slashes. - * If not included, the leading path separator will be added. The - * result will be of this form, which is parsed using {@link #createURI(String) - * createURI}: - *

-	 *   platform:/resource/project-name/path
- * - *

This scheme supports relocatable projects in Eclipse and in - * stand-alone EMF. - * - *

Depending on the encode argument, the path may be - * automatically encoded to escape all spaces, # characters, - * and other characters disallowed in URIs, as well as ?, - * which would delimit a path from a query. Decoding can be performed with - * the static {@link #decode(String) decode} method. It is strongly - * recommended to specify true to enable encoding, unless the - * path string has already been encoded. - * - * @exception java.lang.IllegalArgumentException if any component parsed - * from the path is not valid according to {@link #validDevice validDevice}, - * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or - * {@link #validFragment validFragment}. - * - * @see org.eclipse.core.runtime.Platform#resolve - */ - public static URI createPlatformResourceURI(String pathName, boolean encode) - { - return createPlatformURI("platform:/resource", "platform:/resource/", pathName, encode); - } - - /** - * Static factory method based on parsing a plug-in-based path string, - * with an option to encode the created URI. - * - *

The pathName must be of the form: - *

-	 *   /plugin-id/path
- * - *

Platform-specific path separators will be converted to slashes. - * If not included, the leading path separator will be added. The - * result will be of this form, which is parsed using {@link #createURI(String) - * createURI}: - *

-	 *   platform:/plugin/plugin-id/path
- * - *

This scheme supports relocatable plug-in content in Eclipse. - * - *

Depending on the encode argument, the path may be - * automatically encoded to escape all spaces, # characters, - * and other characters disallowed in URIs, as well as ?, - * which would delimit a path from a query. Decoding can be performed with - * the static {@link #decode(String) decode} method. It is strongly - * recommended to specify true to enable encoding, unless the - * path string has already been encoded. - * - * @exception java.lang.IllegalArgumentException if any component parsed - * from the path is not valid according to {@link #validDevice validDevice}, - * {@link #validSegments validSegments}, {@link #validQuery validQuery}, or - * {@link #validFragment validFragment}. - * - * @see org.eclipse.core.runtime.Platform#resolve - * @since org.eclipse.emf.common 2.3 - */ - public static URI createPlatformPluginURI(String pathName, boolean encode) - { - return createPlatformURI("platform:/plugin", "platform:/plugin/", pathName, encode); - } - - // Private constructor for use of platform factory methods. - private static URI createPlatformURI(String unrootedBase, String rootedBase, String pathName, boolean encode) - { - if (File.separatorChar != SEGMENT_SEPARATOR) - { - pathName = pathName.replace(File.separatorChar, SEGMENT_SEPARATOR); - } - - if (encode) - { - pathName = encode(pathName, PATH_CHAR_HI, PATH_CHAR_LO, false); - } - return createURI((pathName.charAt(0) == SEGMENT_SEPARATOR ? unrootedBase : rootedBase) + pathName); - } - - // Private constructor for use of static factory methods. - private URI(boolean hierarchical, String scheme, String authority, - String device, boolean absolutePath, String[] segments, - String query, String fragment) - { - int tmpHashCode = 0; - if (scheme != null) - { - tmpHashCode ^= scheme.toLowerCase().hashCode(); - } - tmpHashCode ^= Objects.hashCode(authority); - tmpHashCode ^= Objects.hashCode(device); - tmpHashCode ^= Objects.hashCode(query); - tmpHashCode ^= Objects.hashCode(fragment); - for (String segment : segments) { - tmpHashCode ^= segment.hashCode(); - } - - if (hierarchical) - { - tmpHashCode |= HIERARICHICAL_FLAG; - } - else - { - tmpHashCode &= ~HIERARICHICAL_FLAG; - } - if (absolutePath) - { - tmpHashCode |= ABSOLUTE_PATH_FLAG; - } - else - { - tmpHashCode &= ~ABSOLUTE_PATH_FLAG; - } - this.hashCode = tmpHashCode; - this.scheme = scheme == null ? null : scheme.intern(); - this.authority = authority; - this.device = device; - this.segments = segments; - this.query = query; - this.fragment = fragment; - } - - // Validates all of the URI components. Factory methods should call this - // before using the constructor, though they must ensure that the - // inter-component requirements described in their own Javadocs are all - // satisfied, themselves. If a new URI is being constructed out of - // an existing URI, this need not be called. Instead, just the new - // components may be validated individually. - private static void validateURI(boolean hierarchical, String scheme, - String authority, String device, - String[] segments, - String query, String fragment) - { - if (!validScheme(scheme)) - { - throw new IllegalArgumentException("invalid scheme: " + scheme); - } - if (!hierarchical && !validOpaquePart(authority)) - { - throw new IllegalArgumentException("invalid opaquePart: " + authority); - } - if (hierarchical && !isArchiveScheme(scheme) && !validAuthority(authority)) - { - throw new IllegalArgumentException("invalid authority: " + authority); - } - if (hierarchical && isArchiveScheme(scheme) && !validArchiveAuthority(authority)) - { - throw new IllegalArgumentException("invalid authority: " + authority); - } - if (!validDevice(device)) - { - throw new IllegalArgumentException("invalid device: " + device); - } - if (!validSegments(segments)) - { - String s = segments == null ? "invalid segments: null" : - "invalid segment: " + firstInvalidSegment(segments); - throw new IllegalArgumentException(s); - } - if (!validQuery(query)) - { - throw new IllegalArgumentException("invalid query: " + query); - } - if (!validFragment(fragment)) - { - throw new IllegalArgumentException("invalid fragment: " + fragment); - } - } - - // Alternate, stricter implementations of the following validation methods - // are provided, commented out, for possible future use... - - /** - * Returns true if the specified value would be - * valid as the scheme component of a URI; false otherwise. - * - *

A valid scheme may be null or contain any characters except for the - * following: : / ? # - */ - public static boolean validScheme(String value) - { - return value == null || !contains(value, MAJOR_SEPARATOR_HI, MAJOR_SEPARATOR_LO); - - //

A valid scheme may be null, or consist of a single letter followed - // by any number of letters, numbers, and the following characters: - // + - . - - //if (value == null) return true; - //return value.length() != 0 && - // matches(value.charAt(0), ALPHA_HI, ALPHA_LO) && - // validate(value, SCHEME_CHAR_HI, SCHEME_CHAR_LO, false, false); - } - - /** - * Returns true if the specified value would be - * valid as the opaque part component of a URI; false - * otherwise. - * - *

A valid opaque part must be non-null, non-empty, and not contain the - * # character. In addition, its first character must not be - * / - */ - public static boolean validOpaquePart(String value) - { - return value != null && value.indexOf(FRAGMENT_SEPARATOR) == -1 && - value.length() > 0 && value.charAt(0) != SEGMENT_SEPARATOR; - - //

A valid opaque part must be non-null and non-empty. It may contain - // any allowed URI characters, but its first character may not be - // / - - //return value != null && value.length() != 0 && - // value.charAt(0) != SEGMENT_SEPARATOR && - // validate(value, URIC_HI, URIC_LO, true, true); - } - - /** - * Returns true if the specified value would be - * valid as the authority component of a URI; false otherwise. - * - *

A valid authority may be null or contain any characters except for - * the following: / ? # - */ - public static boolean validAuthority(String value) - { - return value == null || !contains(value, SEGMENT_END_HI, SEGMENT_END_LO); - - // A valid authority may be null or contain any allowed URI characters except - // for the following: / ? - - //return value == null || validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true); - } - - /** - * Returns true if the specified value would be - * valid as the authority component of an archive URI; false - * otherwise. - * - *

To be valid, the authority, itself, must be a URI with no fragment, - * followed by the character !. - */ - public static boolean validArchiveAuthority(String value) - { - if (value != null && value.length() > 0 && - value.charAt(value.length() - 1) == ARCHIVE_IDENTIFIER) - { - try - { - URI archiveURI = createURI(value.substring(0, value.length() - 1)); - return !archiveURI.hasFragment(); - } - catch (IllegalArgumentException e) - { - // Ignore the exception and return false. - } - } - return false; - } - - /** - * Tests whether the specified value would be valid as the - * authority component of an archive - * URI. This method has been replaced by {@link #validArchiveAuthority - * validArchiveAuthority} since the same form of URI is now supported - * for schemes other than "jar". This now simply calls that method. - * - * @deprecated As of EMF 2.0, replaced by {@link #validArchiveAuthority - * validArchiveAuthority}. - */ - @Deprecated - public static boolean validJarAuthority(String value) - { - return validArchiveAuthority(value); - } - - /** - * Returns true if the specified value would be - * valid as the device component of a URI; false otherwise. - * - *

A valid device may be null or non-empty, containing any characters - * except for the following: / ? # In addition, its last - * character must be : - */ - public static boolean validDevice(String value) - { - if (value == null) { - return true; - } - int len = value.length(); - return len > 0 && value.charAt(len - 1) == DEVICE_IDENTIFIER && - !contains(value, SEGMENT_END_HI, SEGMENT_END_LO); - } - - /** - * Returns true if the specified value would be - * a valid path segment of a URI; false otherwise. - * - *

A valid path segment must be non-null and not contain any of the - * following characters: / ? # - */ - public static boolean validSegment(String value) - { - return value != null && !contains(value, SEGMENT_END_HI, SEGMENT_END_LO); - - //

A valid path segment must be non-null and may contain any allowed URI - // characters except for the following: / ? - - //return value != null && validate(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, true, true); - } - - /** - * Returns true if the specified value would be - * a valid path segment array of a URI; false otherwise. - * - *

A valid path segment array must be non-null and contain only path - * segments that are valid according to {@link #validSegment validSegment}. - */ - public static boolean validSegments(String[] value) - { - if (value == null) { - return false; - } - for (String segment : value) { - if (!validSegment(segment)) { - return false; - } - } - return true; - } - - // Returns null if the specified value is null or would be a valid path - // segment array of a URI; otherwise, the value of the first invalid - // segment. - private static String firstInvalidSegment(String[] value) - { - if (value == null) { - return null; - } - for (String segment : value) { - if (!validSegment(segment)) { - return segment; - } - } - return null; - } - - /** - * Returns true if the specified value would be - * valid as the query component of a URI; false otherwise. - * - *

A valid query may be null or contain any characters except for - * # - */ - public static boolean validQuery(String value) - { - return value == null || value.indexOf(FRAGMENT_SEPARATOR) == -1; - - //

A valid query may be null or contain any allowed URI characters. - - //return value == null || validate(value, URIC_HI, URIC_LO, true, true); -} - - /** - * Returns true if the specified value would be - * valid as the fragment component of a URI; false otherwise. - * - *

A fragment is taken to be unconditionally valid. - */ - public static boolean validFragment(String value) - { - return true; - - //

A valid fragment may be null or contain any allowed URI characters. - - //return value == null || validate(value, URIC_HI, URIC_LO, true, true); - } - - // Searches the specified string for any characters in the set represented - // by the 128-bit bitmask. Returns true if any occur, or false otherwise. - private static boolean contains(String s, long highBitmask, long lowBitmask) - { - for (int i = 0, len = s.length(); i < len; i++) - { - if (matches(s.charAt(i), highBitmask, lowBitmask)) { - return true; - } - } - return false; - } - - // Tests the non-null string value to see if it contains only ASCII - // characters in the set represented by the specified 128-bit bitmask, - // as well as, optionally, non-ASCII characters 0xA0 and above, and, - // also optionally, escape sequences of % followed by two hex digits. - // This method is used for the new, strict URI validation that is not - // not currently in place. -/* - private static boolean validate(String value, long highBitmask, long lowBitmask, - boolean allowNonASCII, boolean allowEscaped) - { - for (int i = 0, length = value.length(); i < length; i++) - { - char c = value.charAt(i); - - if (matches(c, highBitmask, lowBitmask)) continue; - if (allowNonASCII && c >= 160) continue; - if (allowEscaped && isEscaped(value, i)) - { - i += 2; - continue; - } - return false; - } - return true; - } -*/ - - /** - * Returns true if this is a relative URI, or - * false if it is an absolute URI. - */ - public boolean isRelative() - { - return scheme == null; - } - - /** - * Returns true if this a a hierarchical URI, or - * false if it is of the generic form. - */ - public boolean isHierarchical() - { - return (hashCode & HIERARICHICAL_FLAG) != 0; - } - - /** - * Returns true if this is a hierarchical URI with an authority - * component; false otherwise. - */ - public boolean hasAuthority() - { - return isHierarchical() && authority != null; - } - - /** - * Returns true if this is a non-hierarchical URI with an - * opaque part component; false otherwise. - */ - public boolean hasOpaquePart() - { - // note: hierarchical -> authority != null - return !isHierarchical(); - } - - /** - * Returns true if this is a hierarchical URI with a device - * component; false otherwise. - */ - public boolean hasDevice() - { - // note: device != null -> hierarchical - return device != null; - } - - /** - * Returns true if this is a hierarchical URI with an - * absolute or relative path; false otherwise. - */ - public boolean hasPath() - { - // note: (absolutePath || authority == null) -> hierarchical - // (authority == null && device == null && !absolutePath) -> scheme == null - return hasAbsolutePath() || (authority == null && device == null); - } - - /** - * Returns true if this is a hierarchical URI with an - * absolute path, or false if it is non-hierarchical, has no - * path, or has a relative path. - */ - public boolean hasAbsolutePath() - { - // note: absolutePath -> hierarchical - return (hashCode & ABSOLUTE_PATH_FLAG) != 0; - } - - /** - * Returns true if this is a hierarchical URI with a relative - * path, or false if it is non-hierarchical, has no path, or - * has an absolute path. - */ - public boolean hasRelativePath() - { - // note: authority == null -> hierarchical - // (authority == null && device == null && !absolutePath) -> scheme == null - return authority == null && device == null && !hasAbsolutePath(); - } - - /** - * Returns true if this is a hierarchical URI with an empty - * relative path; false otherwise. - * - *

Note that !hasEmpty() does not imply that this - * URI has any path segments; however, hasRelativePath && - * !hasEmptyPath() does. - */ - public boolean hasEmptyPath() - { - // note: authority == null -> hierarchical - // (authority == null && device == null && !absolutePath) -> scheme == null - return authority == null && device == null && !hasAbsolutePath() && - segments.length == 0; - } - - /** - * Returns true if this is a hierarchical URI with a query - * component; false otherwise. - */ - public boolean hasQuery() - { - // note: query != null -> hierarchical - return query != null; - } - - /** - * Returns true if this URI has a fragment component; - * false otherwise. - */ - public boolean hasFragment() - { - return fragment != null; - } - - /** - * Returns true if this is a current document reference; that - * is, if it is a relative hierarchical URI with no authority, device or - * query components, and no path segments; false is returned - * otherwise. - */ - public boolean isCurrentDocumentReference() - { - // note: authority == null -> hierarchical - // (authority == null && device == null && !absolutePath) -> scheme == null - return authority == null && device == null && !hasAbsolutePath() && - segments.length == 0 && query == null; - } - - /** - * Returns true if this is a {@link - * #isCurrentDocumentReference() current document reference} with no - * fragment component; false otherwise. - * - * @see #isCurrentDocumentReference() - */ - public boolean isEmpty() - { - // note: authority == null -> hierarchical - // (authority == null && device == null && !absolutePath) -> scheme == null - return authority == null && device == null && !hasAbsolutePath() && - segments.length == 0 && query == null && fragment == null; - } - - /** - * Returns true if this is a hierarchical URI that may refer - * directly to a locally accessible file. This is considered to be the - * case for a file-scheme absolute URI, or for a relative URI with no query; - * false is returned otherwise. - */ - public boolean isFile() - { - return isHierarchical() && - ((isRelative() && !hasQuery()) || SCHEME_FILE.equalsIgnoreCase(scheme)); - } - - /** - * Returns true if this is a platform URI, that is, an absolute, - * hierarchical URI, with "platform" scheme, no authority, and at least two - * segments; false is returned otherwise. - * @since org.eclipse.emf.common 2.3 - */ - public boolean isPlatform() - { - return isHierarchical() && !hasAuthority() && segmentCount() >= 2 && - SCHEME_PLATFORM.equalsIgnoreCase(scheme); - } - - /** - * Returns true if this is a platform resource URI, that is, - * a {@link #isPlatform platform URI} whose first segment is "resource"; - * false is returned otherwise. - * @see #isPlatform - * @since org.eclipse.emf.common 2.3 - */ - public boolean isPlatformResource() - { - return isPlatform() && "resource".equals(segments[0]); - } - - /** - * Returns true if this is a platform plug-in URI, that is, - * a {@link #isPlatform platform URI} whose first segment is "plugin"; - * false is returned otherwise. - * @see #isPlatform - * @since org.eclipse.emf.common 2.3 - */ - public boolean isPlatformPlugin() - { - return isPlatform() && "plugin".equals(segments[0]); - } - - /** - * Returns true if this is an archive URI. If so, it is also - * hierarchical, with an authority (consisting of an absolute URI followed - * by "!"), no device, and an absolute path. - */ - public boolean isArchive() - { - return isArchiveScheme(scheme); - } - - /** - * Returns true if the specified value would be - * valid as the scheme of an archive URI; false - * otherwise. - */ - public static boolean isArchiveScheme(String value) - { - // Returns true if the given value is an archive scheme, as defined by - // the org.eclipse.emf.common.util.URI.archiveSchemes system property. - // By default, "jar", "zip", and "archive" are considered archives. - return value != null && archiveSchemes.contains(value.toLowerCase()); - } - - /** - * Returns the hash code. - */ - @Override - public int hashCode() - { - return hashCode; - } - - /** - * Returns true if object is an instance of - * URI equal to this one; false otherwise. - * - *

Equality is determined strictly by comparing components, not by - * attempting to interpret what resource is being identified. The - * comparison of schemes is case-insensitive. - */ - @Override - public boolean equals(Object object) - { - if (this == object) { - return true; - } - if (!(object instanceof URI uri)) { - return false; - } - return hashCode == uri.hashCode() && - equals(scheme, uri.scheme(), true) && - equals(authority, isHierarchical() ? uri.authority() : uri.opaquePart()) && - equals(device, uri.device()) && - equals(query, uri.query()) && - equals(fragment, uri.fragment()) && - segmentsEqual(uri); - } - - // Tests whether this URI's path segment array is equal to that of the - // given uri. - private boolean segmentsEqual(URI uri) - { - if (segments.length != uri.segmentCount()) { - return false; - } - for (int i = 0, len = segments.length; i < len; i++) - { - if (!segments[i].equals(uri.segment(i))) { - return false; - } - } - return true; - } - - // Tests two objects for equality, tolerating nulls; null is considered - // to be a valid value that is only equal to itself. - private static boolean equals(Object o1, Object o2) - { - return o1 == null ? o2 == null : o1.equals(o2); - } - - // Tests two strings for equality, tolerating nulls and optionally - // ignoring case. - private static boolean equals(String s1, String s2, boolean ignoreCase) - { - return s1 == null ? s2 == null : - ignoreCase ? s1.equalsIgnoreCase(s2) : s1.equals(s2); - } - - /** - * If this is an absolute URI, returns the scheme component; - * null otherwise. - */ - public String scheme() - { - return scheme; - } - - /** - * If this is a non-hierarchical URI, returns the opaque part component; - * null otherwise. - */ - public String opaquePart() - { - return isHierarchical() ? null : authority; - } - - /** - * If this is a hierarchical URI with an authority component, returns it; - * null otherwise. - */ - public String authority() - { - return isHierarchical() ? authority : null; - } - - /** - * If this is a hierarchical URI with an authority component that has a - * user info portion, returns it; null otherwise. - */ - public String userInfo() - { - if (!hasAuthority()) { - return null; - } - - int i = authority.indexOf(USER_INFO_SEPARATOR); - return i < 0 ? null : authority.substring(0, i); - } - - /** - * If this is a hierarchical URI with an authority component that has a - * host portion, returns it; null otherwise. - */ - public String host() - { - if (!hasAuthority()) { - return null; - } - - int i = authority.indexOf(USER_INFO_SEPARATOR); - int j = authority.indexOf(PORT_SEPARATOR); - return j < 0 ? authority.substring(i + 1) : authority.substring(i + 1, j); - } - - /** - * If this is a hierarchical URI with an authority component that has a - * port portion, returns it; null otherwise. - */ - public String port() - { - if (!hasAuthority()) { - return null; - } - - int i = authority.indexOf(PORT_SEPARATOR); - return i < 0 ? null : authority.substring(i + 1); - } - - /** - * If this is a hierarchical URI with a device component, returns it; - * null otherwise. - */ - public String device() - { - return device; - } - - /** - * If this is a hierarchical URI with a path, returns an array containing - * the segments of the path; an empty array otherwise. The leading - * separator in an absolute path is not represented in this array, but a - * trailing separator is represented by an empty-string segment as the - * final element. - */ - public String[] segments() - { - return segments.clone(); - } - - /** - * Returns an unmodifiable list containing the same segments as the array - * returned by {@link #segments segments}. - */ - public List segmentsList() - { - return Collections.unmodifiableList(Arrays.asList(segments)); - } - - /** - * Returns the number of elements in the segment array that would be - * returned by {@link #segments segments}. - */ - public int segmentCount() - { - return segments.length; - } - - /** - * Provides fast, indexed access to individual segments in the path - * segment array. - * - * @exception java.lang.IndexOutOfBoundsException if i < 0 or - * i >= segmentCount(). - */ - public String segment(int i) - { - return segments[i]; - } - - /** - * Returns the last segment in the segment array, or null. - */ - public String lastSegment() - { - int len = segments.length; - if (len == 0) { - return null; - } - return segments[len - 1]; - } - - /** - * If this is a hierarchical URI with a path, returns a string - * representation of the path; null otherwise. The path - * consists of a leading segment separator character (a slash), if the - * path is absolute, followed by the slash-separated path segments. If - * this URI has a separate device - * component, it is not included in the path. - */ - public String path() - { - if (!hasPath()) { - return null; - } - - StringBuilder result = new StringBuilder(); - if (hasAbsolutePath()) { - result.append(SEGMENT_SEPARATOR); - } - - for (int i = 0, len = segments.length; i < len; i++) - { - if (i != 0) { - result.append(SEGMENT_SEPARATOR); - } - result.append(segments[i]); - } - return result.toString(); - } - - /** - * If this is a hierarchical URI with a path, returns a string - * representation of the path, including the authority and the - * device component; - * null otherwise. - * - *

If there is no authority, the format of this string is: - *

-	 *   device/pathSegment1/pathSegment2...
- * - *

If there is an authority, it is: - *

-	 *   //authority/device/pathSegment1/pathSegment2...
- * - *

For an archive URI, it's just: - *

-	 *   authority/pathSegment1/pathSegment2...
- */ - public String devicePath() - { - if (!hasPath()) { - return null; - } - - StringBuilder result = new StringBuilder(); - - if (hasAuthority()) - { - if (!isArchive()) { - result.append(AUTHORITY_SEPARATOR); - } - result.append(authority); - - if (hasDevice()) { - result.append(SEGMENT_SEPARATOR); - } - } - - if (hasDevice()) { - result.append(device); - } - if (hasAbsolutePath()) { - result.append(SEGMENT_SEPARATOR); - } - - for (int i = 0, len = segments.length; i < len; i++) - { - if (i != 0) { - result.append(SEGMENT_SEPARATOR); - } - result.append(segments[i]); - } - return result.toString(); - } - - /** - * If this is a hierarchical URI with a query component, returns it; - * null otherwise. - */ - public String query() - { - return query; - } - - - /** - * Returns the URI formed from this URI and the given query. - * - * @exception java.lang.IllegalArgumentException if - * query is not a valid query (portion) according - * to {@link #validQuery validQuery}. - */ - public URI appendQuery(String query) - { - if (!validQuery(query)) - { - throw new IllegalArgumentException( - "invalid query portion: " + query); - } - return new URI(isHierarchical(), scheme, authority, device, hasAbsolutePath(), segments, query, fragment); - } - - /** - * If this URI has a non-null {@link #query query}, returns the URI - * formed by removing it; this URI unchanged, otherwise. - */ - public URI trimQuery() - { - if (query == null) - { - return this; - } - else - { - return new URI(isHierarchical(), scheme, authority, device, hasAbsolutePath(), segments, null, fragment); - } - } - - /** - * If this URI has a fragment component, returns it; null - * otherwise. - */ - public String fragment() - { - return fragment; - } - - /** - * Returns the URI formed from this URI and the given fragment. - * - * @exception java.lang.IllegalArgumentException if - * fragment is not a valid fragment (portion) according - * to {@link #validFragment validFragment}. - */ - public URI appendFragment(String fragment) - { - if (!validFragment(fragment)) - { - throw new IllegalArgumentException( - "invalid fragment portion: " + fragment); - } - URI result = new URI(isHierarchical(), scheme, authority, device, hasAbsolutePath(), segments, query, fragment); - - if (!hasFragment()) - { - result.cachedTrimFragment = this; - } - return result; - } - - /** - * If this URI has a non-null {@link #fragment fragment}, returns the URI - * formed by removing it; this URI unchanged, otherwise. - */ - public URI trimFragment() - { - if (fragment == null) - { - return this; - } - else if (cachedTrimFragment == null) - { - cachedTrimFragment = new URI(isHierarchical(), scheme, authority, device, hasAbsolutePath(), segments, query, null); - } - - return cachedTrimFragment; - } - - /** - * Resolves this URI reference against a base absolute - * hierarchical URI, returning the resulting absolute URI. If already - * absolute, the URI itself is returned. URI resolution is described in - * detail in section 5.2 of RFC - * 2396, "Resolving Relative References to Absolute Form." - * - *

During resolution, empty segments, self references ("."), and parent - * references ("..") are interpreted, so that they can be removed from the - * path. Step 6(g) gives a choice of how to handle the case where parent - * references point to a path above the root: the offending segments can - * be preserved or discarded. This method preserves them. To have them - * discarded, please use the two-parameter form of {@link - * #resolve(URI, boolean) resolve}. - * - * @exception java.lang.IllegalArgumentException if base is - * non-hierarchical or is relative. - */ - public URI resolve(URI base) - { - return resolve(base, true); - } - - /** - * Resolves this URI reference against a base absolute - * hierarchical URI, returning the resulting absolute URI. If already - * absolute, the URI itself is returned. URI resolution is described in - * detail in section 5.2 of RFC - * 2396, "Resolving Relative References to Absolute Form." - * - *

During resolution, empty segments, self references ("."), and parent - * references ("..") are interpreted, so that they can be removed from the - * path. Step 6(g) gives a choice of how to handle the case where parent - * references point to a path above the root: the offending segments can - * be preserved or discarded. This method can do either. - * - * @param preserveRootParents true if segments referring to the - * parent of the root path are to be preserved; false if they - * are to be discarded. - * - * @exception java.lang.IllegalArgumentException if base is - * non-hierarchical or is relative. - */ - public URI resolve(URI base, boolean preserveRootParents) - { - if (!base.isHierarchical() || base.isRelative()) - { - throw new IllegalArgumentException( - "resolve against non-hierarchical or relative base"); - } - - // an absolute URI needs no resolving - if (!isRelative()) { - return this; - } - - // note: isRelative() -> hierarchical - - String newAuthority = authority; - String newDevice = device; - boolean newAbsolutePath = hasAbsolutePath(); - String[] newSegments = segments; - String newQuery = query; - // note: it's okay for two URIs to share a segments array, since - // neither will ever modify it - - if (authority == null) - { - // no authority: use base's - newAuthority = base.authority(); - - if (device == null) - { - // no device: use base's - newDevice = base.device(); - - if (hasEmptyPath() && query == null) - { - // current document reference: use base path and query - newAbsolutePath = base.hasAbsolutePath(); - newSegments = base.segments(); - newQuery = base.query(); - } - else if (hasRelativePath()) - { - // relative path: merge with base and keep query (note: if the - // base has no path and this a non-empty relative path, there is - // an implied root in the resulting path) - newAbsolutePath = base.hasAbsolutePath() || !hasEmptyPath(); - newSegments = newAbsolutePath ? mergePath(base, preserveRootParents) - : NO_SEGMENTS; - } - // else absolute path: keep it and query - } - // else keep device, path, and query - } - // else keep authority, device, path, and query - - // always keep fragment, even if null, and use scheme from base; - // no validation needed since all components are from existing URIs - return new URI(true, base.scheme(), newAuthority, newDevice, - newAbsolutePath, newSegments, newQuery, fragment); - } - - // Merges this URI's relative path with the base non-relative path. If - // base has no path, treat it as the root absolute path, unless this has - // no path either. - private String[] mergePath(URI base, boolean preserveRootParents) - { - if (base.hasRelativePath()) - { - throw new IllegalArgumentException("merge against relative path"); - } - if (!hasRelativePath()) - { - throw new IllegalStateException("merge non-relative path"); - } - - int baseSegmentCount = base.segmentCount(); - int segmentCount = segments.length; - String[] stack = new String[baseSegmentCount + segmentCount]; - int sp = 0; - - // use a stack to accumulate segments of base, except for the last - // (i.e. skip trailing separator and anything following it), and of - // relative path - for (int i = 0; i < baseSegmentCount - 1; i++) - { - sp = accumulate(stack, sp, base.segment(i), preserveRootParents); - } - - for (int i = 0; i < segmentCount; i++) - { - sp = accumulate(stack, sp, segments[i], preserveRootParents); - } - - // if the relative path is empty or ends in an empty segment, a parent - // reference, or a self reference, add a trailing separator to a - // non-empty path - if (sp > 0 && (segmentCount == 0 || - SEGMENT_EMPTY.equals(segments[segmentCount - 1]) || - SEGMENT_PARENT.equals(segments[segmentCount - 1]) || - SEGMENT_SELF.equals(segments[segmentCount - 1]))) - { - stack[sp++] = SEGMENT_EMPTY; - } - - // return a correctly sized result - String[] result = new String[sp]; - System.arraycopy(stack, 0, result, 0, sp); - return result; - } - - // Adds a segment to a stack, skipping empty segments and self references, - // and interpreting parent references. - private static int accumulate(String[] stack, int sp, String segment, - boolean preserveRootParents) - { - if (SEGMENT_PARENT.equals(segment)) - { - if (sp == 0) - { - // special care must be taken for a root's parent reference: it is - // either ignored or the symbolic reference itself is pushed - if (preserveRootParents) { - stack[sp++] = segment; - } - } else // unless we're already accumulating root parent references, - // parent references simply pop the last segment descended - if (SEGMENT_PARENT.equals(stack[sp - 1])) { - stack[sp++] = segment; -} else { - sp--; -} - } - else if (!SEGMENT_EMPTY.equals(segment) && !SEGMENT_SELF.equals(segment)) - { - // skip empty segments and self references; push everything else - stack[sp++] = segment; - } - return sp; - } - - /** - * Finds the shortest relative or, if necessary, the absolute URI that, - * when resolved against the given base absolute hierarchical - * URI using {@link #resolve(URI) resolve}, will yield this absolute URI. - * If base is non-hierarchical or is relative, - * or this is non-hierarchical or is relative, - * this will be returned. - */ - public URI deresolve(URI base) - { - return deresolve(base, true, false, true); - } - - /** - * Finds an absolute URI that, when resolved against the given - * base absolute hierarchical URI using {@link - * #resolve(URI, boolean) resolve}, will yield this absolute URI. - * If base is non-hierarchical or is relative, - * or this is non-hierarchical or is relative, - * this will be returned. - * - * @param preserveRootParents the boolean argument to resolve(URI, - * boolean) for which the returned URI should resolve to this URI. - * @param anyRelPath if true, the returned URI's path (if - * any) will be relative, if possible. If false, the form of - * the result's path will depend upon the next parameter. - * @param shorterRelPath if anyRelPath is false - * and this parameter is true, the returned URI's path (if - * any) will be relative, if one can be found that is no longer (by number - * of segments) than the absolute path. If both anyRelPath - * and this parameter are false, it will be absolute. - */ - public URI deresolve(URI base, boolean preserveRootParents, - boolean anyRelPath, boolean shorterRelPath) - { - if (!base.isHierarchical() || base.isRelative()) { - return this; - } - - if (isRelative()) { - return this; - } - - // note: these assertions imply that neither this nor the base URI has a - // relative path; thus, both have either an absolute path or no path - - // different scheme: need complete, absolute URI - if (!scheme.equalsIgnoreCase(base.scheme())) { - return this; - } - - // since base must be hierarchical, and since a non-hierarchical URI - // must have both scheme and opaque part, the complete absolute URI is - // needed to resolve to a non-hierarchical URI - if (!isHierarchical()) { - return this; - } - - String newAuthority = authority; - String newDevice = device; - boolean newAbsolutePath = hasAbsolutePath(); - String[] newSegments = segments; - String newQuery = query; - - if (equals(authority, base.authority()) && - (hasDevice() || hasPath() || (!base.hasDevice() && !base.hasPath()))) - { - // matching authorities and no device or path removal - newAuthority = null; - - if (equals(device, base.device()) && (hasPath() || !base.hasPath())) - { - // matching devices and no path removal - newDevice = null; - - // exception if (!hasPath() && base.hasPath()) - - if (!anyRelPath && !shorterRelPath) - { - // user rejects a relative path: keep absolute or no path - } - else if (hasPath() == base.hasPath() && segmentsEqual(base) && - equals(query, base.query())) - { - // current document reference: keep no path or query - newAbsolutePath = false; - newSegments = NO_SEGMENTS; - newQuery = null; - } - else if (!hasPath() && !base.hasPath()) - { - // no paths: keep query only - newAbsolutePath = false; - newSegments = NO_SEGMENTS; - } - // exception if (!hasAbsolutePath()) - else if (hasCollapsableSegments(preserveRootParents)) - { - // path form demands an absolute path: keep it and query - } - else - { - // keep query and select relative or absolute path based on length - String[] rel = findRelativePath(base, preserveRootParents); - if (anyRelPath || segments.length > rel.length) - { - // user demands a relative path or the absolute path is longer - newAbsolutePath = false; - newSegments = rel; - } - // else keep shorter absolute path - } - } - // else keep device, path, and query - } - // else keep authority, device, path, and query - - // always include fragment, even if null; - // no validation needed since all components are from existing URIs - return new URI(true, null, newAuthority, newDevice, newAbsolutePath, - newSegments, newQuery, fragment); - } - - // Returns true if the non-relative path includes segments that would be - // collapsed when resolving; false otherwise. If preserveRootParents is - // true, collapsible segments include any empty segments, except for the - // last segment, as well as and parent and self references. If - // preserveRootsParents is false, parent references are not collapsible if - // they are the first segment or preceded only by other parent - // references. - private boolean hasCollapsableSegments(boolean preserveRootParents) - { - if (hasRelativePath()) - { - throw new IllegalStateException("test collapsability of relative path"); - } - - for (int i = 0, len = segments.length; i < len; i++) - { - String segment = segments[i]; - if ((i < len - 1 && SEGMENT_EMPTY.equals(segment)) || - SEGMENT_SELF.equals(segment) || - SEGMENT_PARENT.equals(segment) && ( - !preserveRootParents || ( - i != 0 && !SEGMENT_PARENT.equals(segments[i - 1])))) - { - return true; - } - } - return false; - } - - // Returns the shortest relative path between the the non-relative path of - // the given base and this absolute path. If the base has no path, it is - // treated as the root absolute path. - private String[] findRelativePath(URI base, boolean preserveRootParents) - { - if (base.hasRelativePath()) - { - throw new IllegalArgumentException( - "find relative path against base with relative path"); - } - if (!hasAbsolutePath()) - { - throw new IllegalArgumentException( - "find relative path of non-absolute path"); - } - - // treat an empty base path as the root absolute path - String[] startPath = base.collapseSegments(preserveRootParents); - String[] endPath = segments; - - // drop last segment from base, as in resolving - int startCount = startPath.length > 0 ? startPath.length - 1 : 0; - int endCount = endPath.length; - - // index of first segment that is different between endPath and startPath - int diff = 0; - - // if endPath is shorter than startPath, the last segment of endPath may - // not be compared: because startPath has been collapsed and had its - // last segment removed, all preceding segments can be considered non- - // empty and followed by a separator, while the last segment of endPath - // will either be non-empty and not followed by a separator, or just empty - for (int count = startCount < endCount ? startCount : endCount - 1; - diff < count && startPath[diff].equals(endPath[diff]); diff++) - { - // Empty statement. - } - - int upCount = startCount - diff; - int downCount = endCount - diff; - - // a single separator, possibly preceded by some parent reference - // segments, is redundant - if (downCount == 1 && SEGMENT_EMPTY.equals(endPath[endCount - 1])) - { - downCount = 0; - } - - // an empty path needs to be replaced by a single "." if there is no - // query, to distinguish it from a current document reference - if (upCount + downCount == 0) - { - if (query == null) { - return new String[] { SEGMENT_SELF }; - } - return NO_SEGMENTS; - } - - // return a correctly sized result - String[] result = new String[upCount + downCount]; - Arrays.fill(result, 0, upCount, SEGMENT_PARENT); - System.arraycopy(endPath, diff, result, upCount, downCount); - return result; - } - - // Collapses non-ending empty segments, parent references, and self - // references in a non-relative path, returning the same path that would - // be produced from the base hierarchical URI as part of a resolve. - String[] collapseSegments(boolean preserveRootParents) - { - if (hasRelativePath()) - { - throw new IllegalStateException("collapse relative path"); - } - - if (!hasCollapsableSegments(preserveRootParents)) { - return segments(); - } - - // use a stack to accumulate segments - int segmentCount = segments.length; - String[] stack = new String[segmentCount]; - int sp = 0; - - for (int i = 0; i < segmentCount; i++) - { - sp = accumulate(stack, sp, segments[i], preserveRootParents); - } - - // if the path is non-empty and originally ended in an empty segment, a - // parent reference, or a self reference, add a trailing separator - if (sp > 0 && (SEGMENT_EMPTY.equals(segments[segmentCount - 1]) || - SEGMENT_PARENT.equals(segments[segmentCount - 1]) || - SEGMENT_SELF.equals(segments[segmentCount - 1]))) - { - stack[sp++] = SEGMENT_EMPTY; - } - - // return a correctly sized result - String[] result = new String[sp]; - System.arraycopy(stack, 0, result, 0, sp); - return result; - } - - /** - * Returns the string representation of this URI. For a generic, - * non-hierarchical URI, this looks like: - *

-	 *   scheme:opaquePart#fragment
- * - *

For a hierarchical URI, it looks like: - *

-	 *   scheme://authority/device/pathSegment1/pathSegment2...?query#fragment
- * - *

For an archive URI, it's just: - *

-	 *   scheme:authority/pathSegment1/pathSegment2...?query#fragment
- *

Of course, absent components and their separators will be omitted. - */ - @Override - public String toString() - { - if (cachedToString == null) - { - StringBuilder result = new StringBuilder(); - if (!isRelative()) - { - result.append(scheme); - result.append(SCHEME_SEPARATOR); - } - - if (isHierarchical()) - { - if (hasAuthority()) - { - if (!isArchive()) { - result.append(AUTHORITY_SEPARATOR); - } - result.append(authority); - } - - if (hasDevice()) - { - result.append(SEGMENT_SEPARATOR); - result.append(device); - } - - if (hasAbsolutePath()) { - result.append(SEGMENT_SEPARATOR); - } - - for (int i = 0, len = segments.length; i < len; i++) - { - if (i != 0) { - result.append(SEGMENT_SEPARATOR); - } - result.append(segments[i]); - } - - if (hasQuery()) - { - result.append(QUERY_SEPARATOR); - result.append(query); - } - } - else - { - result.append(authority); - } - - if (hasFragment()) - { - result.append(FRAGMENT_SEPARATOR); - result.append(fragment); - } - cachedToString = result.toString(); - } - return cachedToString; - } - - // Returns a string representation of this URI for debugging, explicitly - // showing each of the components. - String toString(boolean includeSimpleForm) - { - StringBuilder result = new StringBuilder(); - if (includeSimpleForm) { - result.append(toString()); - } - result.append("\n hierarchical: "); - result.append(isHierarchical()); - result.append("\n scheme: "); - result.append(scheme); - result.append("\n authority: "); - result.append(authority); - result.append("\n device: "); - result.append(device); - result.append("\n absolutePath: "); - result.append(hasAbsolutePath()); - result.append("\n segments: "); - if (segments.length == 0) { - result.append(""); - } - for (int i = 0, len = segments.length; i < len; i++) - { - if (i > 0) { - result.append("\n "); - } - result.append(segments[i]); - } - result.append("\n query: "); - result.append(query); - result.append("\n fragment: "); - result.append(fragment); - return result.toString(); - } - - /** - * If this URI may refer directly to a locally accessible file, as - * determined by {@link #isFile isFile}, {@link #decode decodes} and formats - * the URI as a pathname to that file; returns null otherwise. - * - *

If there is no authority, the format of this string is: - *

-	 *   device/pathSegment1/pathSegment2...
- * - *

If there is an authority, it is: - *

-	 *   //authority/device/pathSegment1/pathSegment2...
- * - *

However, the character used as a separator is system-dependent and - * obtained from {@link java.io.File#separatorChar}. - */ - public String toFileString() - { - if (!isFile()) { - return null; - } - - StringBuilder result = new StringBuilder(); - char separator = File.separatorChar; - - if (hasAuthority()) - { - result.append(separator); - result.append(separator); - result.append(authority); - - if (hasDevice()) { - result.append(separator); - } - } - - if (hasDevice()) { - result.append(device); - } - if (hasAbsolutePath()) { - result.append(separator); - } - - for (int i = 0, len = segments.length; i < len; i++) - { - if (i != 0) { - result.append(separator); - } - result.append(segments[i]); - } - - return decode(result.toString()); - } - - /** - * If this is a platform URI, as determined by {@link #isPlatform}, returns - * the workspace-relative or plug-in-based path to the resource, optionally - * {@link #decode decoding} the segments in the process. - * @see #createPlatformResourceURI(String, boolean) - * @see #createPlatformPluginURI - * @since org.eclipse.emf.common 2.3 - */ - public String toPlatformString(boolean decode) - { - if (isPlatform()) - { - StringBuilder result = new StringBuilder(); - for (int i = 1, len = segments.length; i < len; i++) - { - result.append('/').append(decode ? URI.decode(segments[i]) : segments[i]); - } - return result.toString(); - } - return null; - } - - /** - * Returns the URI formed by appending the specified segment on to the end - * of the path of this URI, if hierarchical; this URI unchanged, - * otherwise. If this URI has an authority and/or device, but no path, - * the segment becomes the first under the root in an absolute path. - * - * @exception java.lang.IllegalArgumentException if segment - * is not a valid segment according to {@link #validSegment}. - */ - public URI appendSegment(String segment) - { - if (!validSegment(segment)) - { - throw new IllegalArgumentException("invalid segment: " + segment); - } - - if (!isHierarchical()) { - return this; - } - - // absolute path or no path -> absolute path - boolean newAbsolutePath = !hasRelativePath(); - - int len = segments.length; - String[] newSegments = new String[len + 1]; - System.arraycopy(segments, 0, newSegments, 0, len); - newSegments[len] = segment; - - return new URI(true, scheme, authority, device, newAbsolutePath, - newSegments, query, fragment); - } - - /** - * Returns the URI formed by appending the specified segments on to the - * end of the path of this URI, if hierarchical; this URI unchanged, - * otherwise. If this URI has an authority and/or device, but no path, - * the segments are made to form an absolute path. - * - * @param segments an array of non-null strings, each representing one - * segment of the path. If desired, a trailing separator should be - * represented by an empty-string segment as the last element of the - * array. - * - * @exception java.lang.IllegalArgumentException if segments - * is not a valid segment array according to {@link #validSegments}. - */ - public URI appendSegments(String[] segments) - { - if (!validSegments(segments)) - { - String s = segments == null ? "invalid segments: null" : - "invalid segment: " + firstInvalidSegment(segments); - throw new IllegalArgumentException(s); - } - - if (!isHierarchical()) { - return this; - } - - // absolute path or no path -> absolute path - boolean newAbsolutePath = !hasRelativePath(); - - int len = this.segments.length; - int segmentsCount = segments.length; - String[] newSegments = new String[len + segmentsCount]; - System.arraycopy(this.segments, 0, newSegments, 0, len); - System.arraycopy(segments, 0, newSegments, len, segmentsCount); - - return new URI(true, scheme, authority, device, newAbsolutePath, - newSegments, query, fragment); - } - - /** - * Returns the URI formed by trimming the specified number of segments - * (including empty segments, such as one representing a trailing - * separator) from the end of the path of this URI, if hierarchical; - * otherwise, this URI is returned unchanged. - * - *

Note that if all segments are trimmed from an absolute path, the - * root absolute path remains. - * - * @param i the number of segments to be trimmed in the returned URI. If - * less than 1, this URI is returned unchanged; if equal to or greater - * than the number of segments in this URI's path, all segments are - * trimmed. - */ - public URI trimSegments(int i) - { - if (!isHierarchical() || i < 1) { - return this; - } - - String[] newSegments = NO_SEGMENTS; - int len = segments.length - i; - if (len > 0) - { - newSegments = new String[len]; - System.arraycopy(segments, 0, newSegments, 0, len); - } - return new URI(true, scheme, authority, device, hasAbsolutePath(), - newSegments, query, fragment); - } - - /** - * Returns true if this is a hierarchical URI that has a path - * that ends with a trailing separator; false otherwise. - * - *

A trailing separator is represented as an empty segment as the - * last segment in the path; note that this definition does not - * include the lone separator in the root absolute path. - */ - public boolean hasTrailingPathSeparator() - { - return segments.length > 0 && - SEGMENT_EMPTY.equals(segments[segments.length - 1]); - } - - /** - * If this is a hierarchical URI whose path includes a file extension, - * that file extension is returned; null otherwise. We define a file - * extension as any string following the last period (".") in the final - * path segment. If there is no path, the path ends in a trailing - * separator, or the final segment contains no period, then we consider - * there to be no file extension. If the final segment ends in a period, - * then the file extension is an empty string. - */ - public String fileExtension() - { - int len = segments.length; - if (len == 0) { - return null; - } - - String lastSegment = segments[len - 1]; - int i = lastSegment.lastIndexOf(FILE_EXTENSION_SEPARATOR); - return i < 0 ? null : lastSegment.substring(i + 1); - } - - /** - * Returns the URI formed by appending a period (".") followed by the - * specified file extension to the last path segment of this URI, if it is - * hierarchical with a non-empty path ending in a non-empty segment; - * otherwise, this URI is returned unchanged. - - *

The extension is appended regardless of whether the segment already - * contains an extension. - * - * @exception java.lang.IllegalArgumentException if - * fileExtension is not a valid segment (portion) according - * to {@link #validSegment}. - */ - public URI appendFileExtension(String fileExtension) - { - if (!validSegment(fileExtension)) - { - throw new IllegalArgumentException( - "invalid segment portion: " + fileExtension); - } - - int len = segments.length; - if (len == 0) { - return this; - } - - String lastSegment = segments[len - 1]; - if (SEGMENT_EMPTY.equals(lastSegment)) { - return this; - } - StringBuilder newLastSegment = new StringBuilder(lastSegment); - newLastSegment.append(FILE_EXTENSION_SEPARATOR); - newLastSegment.append(fileExtension); - - String[] newSegments = new String[len]; - System.arraycopy(segments, 0, newSegments, 0, len - 1); - newSegments[len - 1] = newLastSegment.toString(); - - // note: segments.length > 0 -> hierarchical - return new URI(true, scheme, authority, device, hasAbsolutePath(), - newSegments, query, fragment); - } - - /** - * If this URI has a non-null {@link #fileExtension fileExtension}, - * returns the URI formed by removing it; this URI unchanged, otherwise. - */ - public URI trimFileExtension() - { - int len = segments.length; - if (len == 0) { - return this; - } - - String lastSegment = segments[len - 1]; - int i = lastSegment.lastIndexOf(FILE_EXTENSION_SEPARATOR); - if (i < 0) { - return this; - } - - String newLastSegment = lastSegment.substring(0, i); - String[] newSegments = new String[len]; - System.arraycopy(segments, 0, newSegments, 0, len - 1); - newSegments[len - 1] = newLastSegment; - - // note: segments.length > 0 -> hierarchical - return new URI(true, scheme, authority, device, hasAbsolutePath(), - newSegments, query, fragment); - } - - /** - * Returns true if this is a hierarchical URI that ends in a - * slash; that is, it has a trailing path separator or is the root - * absolute path, and has no query and no fragment; false - * is returned otherwise. - */ - public boolean isPrefix() - { - return isHierarchical() && query == null && fragment == null && - (hasTrailingPathSeparator() || (hasAbsolutePath() && segments.length == 0)); - } - - /** - * If this is a hierarchical URI reference and oldPrefix is a - * prefix of it, this returns the URI formed by replacing it by - * newPrefix; null otherwise. - * - *

In order to be a prefix, the oldPrefix's - * {@link #isPrefix isPrefix} must return true, and it must - * match this URI's scheme, authority, and device. Also, the paths must - * match, up to prefix's end. - * - * @exception java.lang.IllegalArgumentException if either - * oldPrefix or newPrefix is not a prefix URI - * according to {@link #isPrefix}. - */ - public URI replacePrefix(URI oldPrefix, URI newPrefix) - { - if (!oldPrefix.isPrefix() || !newPrefix.isPrefix()) - { - String which = oldPrefix.isPrefix() ? "new" : "old"; - throw new IllegalArgumentException("non-prefix " + which + " value"); - } - - // Get what's left of the segments after trimming the prefix. - String[] tailSegments = getTailSegments(oldPrefix); - if (tailSegments == null) { - return null; - } - - // If the new prefix has segments, it is not the root absolute path, - // and we need to drop the trailing empty segment and append the tail - // segments. - String[] mergedSegments = tailSegments; - if (newPrefix.segmentCount() != 0) - { - int segmentsToKeep = newPrefix.segmentCount() - 1; - mergedSegments = new String[segmentsToKeep + tailSegments.length]; - System.arraycopy(newPrefix.segments(), 0, mergedSegments, 0, - segmentsToKeep); - - if (tailSegments.length != 0) - { - System.arraycopy(tailSegments, 0, mergedSegments, segmentsToKeep, - tailSegments.length); - } - } - - // no validation needed since all components are from existing URIs - return new URI(true, newPrefix.scheme(), newPrefix.authority(), - newPrefix.device(), newPrefix.hasAbsolutePath(), - mergedSegments, query, fragment); - } - - // If this is a hierarchical URI reference and prefix is a prefix of it, - // returns the portion of the path remaining after that prefix has been - // trimmed; null otherwise. - private String[] getTailSegments(URI prefix) - { - if (!prefix.isPrefix()) - { - throw new IllegalArgumentException("non-prefix trim"); - } - - // Don't even consider it unless this is hierarchical and has scheme, - // authority, device and path absoluteness equal to those of the prefix. - if (!isHierarchical() || - !equals(scheme, prefix.scheme(), true) || - !equals(authority, prefix.authority()) || - !equals(device, prefix.device()) || - hasAbsolutePath() != prefix.hasAbsolutePath()) - { - return null; - } - - // If the prefix has no segments, then it is the root absolute path, and - // we know this is an absolute path, too. - if (prefix.segmentCount() == 0) { - return segments; - } - - // This must have no fewer segments than the prefix. Since the prefix - // is not the root absolute path, its last segment is empty; all others - // must match. - int i = 0; - int segmentsToCompare = prefix.segmentCount() - 1; - if (segments.length <= segmentsToCompare) { - return null; - } - - for (; i < segmentsToCompare; i++) - { - if (!segments[i].equals(prefix.segment(i))) { - return null; - } - } - - // The prefix really is a prefix of this. If this has just one more, - // empty segment, the paths are the same. - if (i == segments.length - 1 && SEGMENT_EMPTY.equals(segments[i])) - { - return NO_SEGMENTS; - } - - // Otherwise, the path needs only the remaining segments. - String[] newSegments = new String[segments.length - i]; - System.arraycopy(segments, i, newSegments, 0, newSegments.length); - return newSegments; - } - - /** - * Encodes a string so as to produce a valid opaque part value, as defined - * by the RFC. All excluded characters, such as space and #, - * are escaped, as is / if it is the first character. - * - * @param ignoreEscaped true to leave % characters - * unescaped if they already begin a valid three-character escape sequence; - * false to encode all % characters. Note that - * if a % is not followed by 2 hex digits, it will always be - * escaped. - */ - public static String encodeOpaquePart(String value, boolean ignoreEscaped) - { - String result = encode(value, URIC_HI, URIC_LO, ignoreEscaped); - return result != null && result.length() > 0 && result.charAt(0) == SEGMENT_SEPARATOR ? - "%2F" + result.substring(1) : - result; - } - - /** - * Encodes a string so as to produce a valid authority, as defined by the - * RFC. All excluded characters, such as space and #, - * are escaped, as are / and ? - * - * @param ignoreEscaped true to leave % characters - * unescaped if they already begin a valid three-character escape sequence; - * false to encode all % characters. Note that - * if a % is not followed by 2 hex digits, it will always be - * escaped. - */ - public static String encodeAuthority(String value, boolean ignoreEscaped) - { - return encode(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, ignoreEscaped); - } - - /** - * Encodes a string so as to produce a valid segment, as defined by the - * RFC. All excluded characters, such as space and #, - * are escaped, as are / and ? - * - * @param ignoreEscaped true to leave % characters - * unescaped if they already begin a valid three-character escape sequence; - * false to encode all % characters. Note that - * if a % is not followed by 2 hex digits, it will always be - * escaped. - */ - public static String encodeSegment(String value, boolean ignoreEscaped) - { - return encode(value, SEGMENT_CHAR_HI, SEGMENT_CHAR_LO, ignoreEscaped); - } - - /** - * Encodes a string so as to produce a valid query, as defined by the RFC. - * Only excluded characters, such as space and #, are escaped. - * - * @param ignoreEscaped true to leave % characters - * unescaped if they already begin a valid three-character escape sequence; - * false to encode all % characters. Note that - * if a % is not followed by 2 hex digits, it will always be - * escaped. - */ - public static String encodeQuery(String value, boolean ignoreEscaped) - { - return encode(value, URIC_HI, URIC_LO, ignoreEscaped); - } - - /** - * Encodes a string so as to produce a valid fragment, as defined by the - * RFC. Only excluded characters, such as space and #, are - * escaped. - * - * @param ignoreEscaped true to leave % characters - * unescaped if they already begin a valid three-character escape sequence; - * false to encode all % characters. Note that - * if a % is not followed by 2 hex digits, it will always be - * escaped. - */ - public static String encodeFragment(String value, boolean ignoreEscaped) - { - return encode(value, URIC_HI, URIC_LO, ignoreEscaped); - } - - // Encodes a complete URI, optionally leaving % characters unescaped when - // beginning a valid three-character escape sequence. We can either treat - // the first or # as a fragment separator, or encode them all. - private static String encodeURI(String uri, boolean ignoreEscaped, int fragmentLocationStyle) - { - if (uri == null) { - return null; - } - - StringBuilder result = new StringBuilder(); - - int i = uri.indexOf(SCHEME_SEPARATOR); - if (i != -1) - { - String scheme = uri.substring(0, i); - result.append(scheme); - result.append(SCHEME_SEPARATOR); - } - - int j = - fragmentLocationStyle == FRAGMENT_FIRST_SEPARATOR ? uri.indexOf(FRAGMENT_SEPARATOR) : - fragmentLocationStyle == FRAGMENT_LAST_SEPARATOR ? uri.lastIndexOf(FRAGMENT_SEPARATOR) : -1; - - if (j != -1) - { - String sspart = uri.substring(++i, j); - result.append(encode(sspart, URIC_HI, URIC_LO, ignoreEscaped)); - result.append(FRAGMENT_SEPARATOR); - - String fragment = uri.substring(++j); - result.append(encode(fragment, URIC_HI, URIC_LO, ignoreEscaped)); - } - else - { - String sspart = uri.substring(++i); - result.append(encode(sspart, URIC_HI, URIC_LO, ignoreEscaped)); - } - - return result.toString(); - } - - // Encodes the given string, replacing each ASCII character that is not in - // the set specified by the 128-bit bitmask and each non-ASCII character - // below 0xA0 by an escape sequence of % followed by two hex digits. If - // % is not in the set but ignoreEscaped is true, then % will not be encoded - // iff it already begins a valid escape sequence. - private static String encode(String value, long highBitmask, long lowBitmask, boolean ignoreEscaped) - { - if (value == null) { - return null; - } - - StringBuilder result = null; - - for (int i = 0, len = value.length(); i < len; i++) - { - char c = value.charAt(i); - - if (!matches(c, highBitmask, lowBitmask) && c < 160 && - (!ignoreEscaped || !isEscaped(value, i))) - { - if (result == null) - { - result = new StringBuilder(value.substring(0, i)); - } - appendEscaped(result, (byte)c); - } - else if (result != null) - { - result.append(c); - } - } - return result == null ? value : result.toString(); - } - - // Tests whether an escape occurs in the given string, starting at index i. - // An escape sequence is a % followed by two hex digits. - private static boolean isEscaped(String s, int i) - { - return s.charAt(i) == ESCAPE && s.length() > i + 2 && - matches(s.charAt(i + 1), HEX_HI, HEX_LO) && - matches(s.charAt(i + 2), HEX_HI, HEX_LO); - } - - // Computes a three-character escape sequence for the byte, appending - // it to the StringBuilder. Only characters up to 0xFF should be escaped; - // all but the least significant byte will be ignored. - private static void appendEscaped(StringBuilder result, byte b) - { - result.append(ESCAPE); - - // The byte is automatically widened into an int, with sign extension, - // for shifting. This can introduce 1's to the left of the byte, which - // must be cleared by masking before looking up the hex digit. - // - result.append(HEX_DIGITS[(b >> 4) & 0x0F]); - result.append(HEX_DIGITS[b & 0x0F]); - } - - /** - * Decodes the given string by interpreting three-digit escape sequences as the bytes of a UTF-8 encoded character - * and replacing them with the characters they represent. - * Incomplete escape sequences are ignored and invalid UTF-8 encoded bytes are treated as extended ASCII characters. - */ - public static String decode(String value) - { - if (value == null) { - return null; - } - - int i = value.indexOf('%'); - if (i < 0) - { - return value; - } - else - { - StringBuilder result = new StringBuilder(value.substring(0, i)); - byte [] bytes = new byte[4]; - int receivedBytes = 0; - int expectedBytes = 0; - for (int len = value.length(); i < len; i++) - { - if (isEscaped(value, i)) - { - char character = unescape(value.charAt(i + 1), value.charAt(i + 2)); - i += 2; - - if (expectedBytes > 0) - { - if ((character & 0xC0) == 0x80) - { - bytes[receivedBytes++] = (byte)character; - } - else - { - expectedBytes = 0; - } - } - else if (character >= 0x80) - { - if ((character & 0xE0) == 0xC0) - { - bytes[receivedBytes++] = (byte)character; - expectedBytes = 2; - } - else if ((character & 0xF0) == 0xE0) - { - bytes[receivedBytes++] = (byte)character; - expectedBytes = 3; - } - else if ((character & 0xF8) == 0xF0) - { - bytes[receivedBytes++] = (byte)character; - expectedBytes = 4; - } - } - - if (expectedBytes > 0) - { - if (receivedBytes == expectedBytes) - { - switch (receivedBytes) - { - case 2: - { - result.append((char)((bytes[0] & 0x1F) << 6 | bytes[1] & 0x3F)); - break; - } - case 3: - { - result.append((char)((bytes[0] & 0xF) << 12 | (bytes[1] & 0X3F) << 6 | bytes[2] & 0x3F)); - break; - } - case 4: - { - result.appendCodePoint(((bytes[0] & 0x7) << 18 | (bytes[1] & 0X3F) << 12 | (bytes[2] & 0X3F) << 6 | bytes[3] & 0x3F)); - break; - } - } - receivedBytes = 0; - expectedBytes = 0; - } - } - else - { - for (int j = 0; j < receivedBytes; ++j) - { - result.append((char)bytes[j]); - } - receivedBytes = 0; - result.append(character); - } - } - else - { - for (int j = 0; j < receivedBytes; ++j) - { - result.append((char)bytes[j]); - } - receivedBytes = 0; - result.append(value.charAt(i)); - } - } - return result.toString(); - } - } - - // Returns the character encoded by % followed by the two given hex digits, - // which is always 0xFF or less, so can safely be casted to a byte. If - // either character is not a hex digit, a bogus result will be returned. - private static char unescape(char highHexDigit, char lowHexDigit) - { - return (char)((valueOf(highHexDigit) << 4) | valueOf(lowHexDigit)); - } - - // Returns the int value of the given hex digit. - private static int valueOf(char hexDigit) - { - if (hexDigit >= 'A' && hexDigit <= 'F') - { - return hexDigit - 'A' + 10; - } - if (hexDigit >= 'a' && hexDigit <= 'f') - { - return hexDigit - 'a' + 10; - } - if (hexDigit >= '0' && hexDigit <= '9') - { - return hexDigit - '0'; - } - return 0; - } -} diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyBorderSWTHandler.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyBorderSWTHandler.java index f8a46ac2858..e7a4c6d0802 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyBorderSWTHandler.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyBorderSWTHandler.java @@ -14,7 +14,6 @@ package org.eclipse.e4.ui.css.swt.properties.css2; import org.eclipse.e4.ui.css.core.dom.properties.CSSBorderProperties; -import org.eclipse.e4.ui.css.core.dom.properties.ICSSPropertyHandler2; import org.eclipse.e4.ui.css.core.dom.properties.css2.AbstractCSSPropertyBorderHandler; import org.eclipse.e4.ui.css.core.dom.properties.css2.ICSSPropertyBorderHandler; import org.eclipse.e4.ui.css.core.engine.CSSEngine; @@ -28,8 +27,7 @@ import org.w3c.dom.css.CSSPrimitiveValue; import org.w3c.dom.css.CSSValue; -public class CSSPropertyBorderSWTHandler extends -AbstractCSSPropertyBorderHandler implements ICSSPropertyHandler2 { +public class CSSPropertyBorderSWTHandler extends AbstractCSSPropertyBorderHandler { public static final ICSSPropertyBorderHandler INSTANCE = new CSSPropertyBorderSWTHandler(); @@ -70,7 +68,7 @@ public boolean applyCSSProperty(Object element, String property, } @Override - public void onAllCSSPropertiesApplyed(Object element, CSSEngine engine) + public void onAllCSSPropertiesApplied(Object element, CSSEngine engine) throws Exception { Control control = SWTElementHelpers.getControl(element); if (control != null) { diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyFontSWTHandler.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyFontSWTHandler.java index 886c70f8051..a5a6afe595b 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyFontSWTHandler.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/css2/CSSPropertyFontSWTHandler.java @@ -16,7 +16,6 @@ *******************************************************************************/ package org.eclipse.e4.ui.css.swt.properties.css2; -import org.eclipse.e4.ui.css.core.dom.properties.ICSSPropertyHandler2; import org.eclipse.e4.ui.css.core.dom.properties.css2.AbstractCSSPropertyFontHandler; import org.eclipse.e4.ui.css.core.dom.properties.css2.CSS2FontProperties; import org.eclipse.e4.ui.css.core.dom.properties.css2.ICSSPropertyFontHandler; @@ -39,8 +38,7 @@ import org.w3c.dom.css.CSSStyleDeclaration; import org.w3c.dom.css.CSSValue; -public class CSSPropertyFontSWTHandler extends AbstractCSSPropertyFontHandler -implements ICSSPropertyHandler2 { +public class CSSPropertyFontSWTHandler extends AbstractCSSPropertyFontHandler { public static final ICSSPropertyFontHandler INSTANCE = new CSSPropertyFontSWTHandler(); @@ -232,7 +230,7 @@ public String retrieveCSSPropertyFontWeight(Object element, String pseudo, } @Override - public void onAllCSSPropertiesApplyed(Object element, CSSEngine engine) + public void onAllCSSPropertiesApplied(Object element, CSSEngine engine) throws Exception { final Widget widget = SWTElementHelpers.getWidget(element); if (widget == null || widget instanceof CTabItem) { diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/custom/CSSPropertyTabRendererSWTHandler.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/custom/CSSPropertyTabRendererSWTHandler.java index dd98fcd4271..0d05ab5457d 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/custom/CSSPropertyTabRendererSWTHandler.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/custom/CSSPropertyTabRendererSWTHandler.java @@ -17,7 +17,7 @@ import org.eclipse.core.runtime.ILog; import org.eclipse.core.runtime.Platform; import org.eclipse.e4.ui.css.core.engine.CSSEngine; -import org.eclipse.e4.ui.css.swt.helpers.URI; +import org.eclipse.emf.common.util.URI; import org.eclipse.e4.ui.css.swt.properties.AbstractCSSPropertySWTHandler; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabFolderRenderer; diff --git a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/definition/CSSPropertyThemeElementDefinitionHandler.java b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/definition/CSSPropertyThemeElementDefinitionHandler.java index fe63702d7f3..38e20be2314 100644 --- a/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/definition/CSSPropertyThemeElementDefinitionHandler.java +++ b/bundles/org.eclipse.e4.ui.css.swt/src/org/eclipse/e4/ui/css/swt/properties/definition/CSSPropertyThemeElementDefinitionHandler.java @@ -24,7 +24,7 @@ import org.eclipse.e4.ui.css.core.dom.properties.ICSSPropertyHandler; import org.eclipse.e4.ui.css.core.engine.CSSEngine; import org.eclipse.e4.ui.css.swt.dom.definition.ThemeDefinitionElement; -import org.eclipse.e4.ui.css.swt.helpers.URI; +import org.eclipse.emf.common.util.URI; import org.eclipse.e4.ui.internal.css.swt.definition.IThemeElementDefinitionOverridable; import org.eclipse.osgi.service.localization.BundleLocalization; import org.osgi.framework.Bundle; diff --git a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/AbstractCSSEngineTest.java b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/CSSEngineImplTest.java similarity index 84% rename from tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/AbstractCSSEngineTest.java rename to tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/CSSEngineImplTest.java index 4490d49cd68..a6b1e12ce1d 100644 --- a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/AbstractCSSEngineTest.java +++ b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/CSSEngineImplTest.java @@ -17,29 +17,23 @@ import java.util.Objects; -import org.eclipse.e4.ui.css.core.dom.parsers.CSSParser; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.w3c.dom.Element; -public class AbstractCSSEngineTest { +public class CSSEngineImplTest { - private AbstractCSSEngine objectUnderTest; + private CSSEngineImpl objectUnderTest; @BeforeEach public void setUp() { - objectUnderTest = new AbstractCSSEngine() { + objectUnderTest = new CSSEngineImpl() { @Override public void reapply() { // mock does nothing } - @Override - public CSSParser makeCSSParser() { - return null; - } - }; objectUnderTest.setElementProvider((element, engine) -> { // throws NPE if parameter is null diff --git a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/selector/SelectorMatcherTest.java b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/selector/SelectorMatcherTest.java new file mode 100644 index 00000000000..31162dd52c7 --- /dev/null +++ b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/css/core/impl/engine/selector/SelectorMatcherTest.java @@ -0,0 +1,223 @@ +/******************************************************************************* + * Copyright (c) 2026 Lars Vogel 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: + * Lars Vogel - initial API and implementation + *******************************************************************************/ +package org.eclipse.e4.ui.css.core.impl.engine.selector; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.List; + +import org.eclipse.e4.ui.css.core.impl.engine.CSSEngineImpl; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Adjacent; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.And; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.AttributeBeginHyphen; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.AttributeIncludes; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.AttributeSelector; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Child; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.ClassSelector; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Descendant; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.ElementType; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.IdSelector; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.PseudoClass; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.SelectorList; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Universal; +import org.eclipse.e4.ui.tests.css.core.util.TestElement; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link SelectorMatcher}. The cases mirror those in the + * Phase 1 {@code CSSEngineTest}, but go through the new internal selector + * AST instead of SAC. When Phase 3 Step 1 wires the engine to use this + * matcher, the SAC-based duplicate tests can be retired. + */ +class SelectorMatcherTest { + + private static final TestCSSEngine ENGINE = new TestCSSEngine(); + + private static final class TestCSSEngine extends CSSEngineImpl { + @Override + public void reapply() { + } + } + + private static TestElement element(String tag, String cssClass, String id) { + TestElement e = new TestElement(tag, ENGINE); + if (cssClass != null) { + e.setClass(cssClass); + } + if (id != null) { + e.setId(id); + } + return e; + } + + @Test + void universalMatchesAnything() { + assertTrue(SelectorMatcher.matches(new Universal(), element("Button", null, null), null)); + assertTrue(SelectorMatcher.matches(new Universal(), element("Label", "x", "y"), null)); + } + + @Test + void typeSelectorIsCaseSensitive() { + assertTrue(SelectorMatcher.matches(new ElementType("Button"), element("Button", null, null), null)); + assertFalse(SelectorMatcher.matches(new ElementType("Button"), element("button", null, null), null)); + assertFalse(SelectorMatcher.matches(new ElementType("Button"), element("Label", null, null), null)); + } + + @Test + void classSelector() { + assertTrue(SelectorMatcher.matches(new ClassSelector("foo"), element("Button", "foo", null), null)); + assertFalse(SelectorMatcher.matches(new ClassSelector("foo"), element("Button", "bar", null), null)); + assertFalse(SelectorMatcher.matches(new ClassSelector("foo"), element("Button", null, null), null)); + } + + @Test + void classSelectorMatchesOneOfMultipleClasses() { + assertTrue(SelectorMatcher.matches(new ClassSelector("foo"), element("Button", "foo bar", null), null)); + assertTrue(SelectorMatcher.matches(new ClassSelector("bar"), element("Button", "foo bar", null), null)); + assertFalse(SelectorMatcher.matches(new ClassSelector("baz"), element("Button", "foo bar", null), null)); + } + + @Test + void idSelector() { + assertTrue(SelectorMatcher.matches(new IdSelector("go"), element("Button", null, "go"), null)); + assertFalse(SelectorMatcher.matches(new IdSelector("go"), element("Button", null, "stop"), null)); + assertFalse(SelectorMatcher.matches(new IdSelector("go"), element("Button", null, null), null)); + } + + @Test + void compoundSelector() { + Selectors.Selector selector = new And(new And(new ElementType("Button"), new ClassSelector("primary")), + new IdSelector("go")); + assertTrue(SelectorMatcher.matches(selector, element("Button", "primary", "go"), null)); + assertFalse(SelectorMatcher.matches(selector, element("Label", "primary", "go"), null)); + assertFalse(SelectorMatcher.matches(selector, element("Button", "secondary", "go"), null)); + assertFalse(SelectorMatcher.matches(selector, element("Button", "primary", "stop"), null)); + } + + @Test + void descendantCombinator() { + Selectors.Selector selector = new Descendant(new ElementType("Composite"), new ElementType("Button")); + TestElement composite = element("Composite", null, null); + TestElement intermediate = new TestElement("Group", composite, ENGINE); + TestElement button = new TestElement("Button", intermediate, ENGINE); + assertTrue(SelectorMatcher.matches(selector, button, null)); + + TestElement orphan = element("Button", null, null); + assertFalse(SelectorMatcher.matches(selector, orphan, null)); + } + + @Test + void childCombinator() { + Selectors.Selector selector = new Child(new ElementType("Composite"), new ElementType("Button")); + TestElement composite = element("Composite", null, null); + TestElement direct = new TestElement("Button", composite, ENGINE); + assertTrue(SelectorMatcher.matches(selector, direct, null)); + + TestElement intermediate = new TestElement("Group", composite, ENGINE); + TestElement grandchild = new TestElement("Button", intermediate, ENGINE); + assertFalse(SelectorMatcher.matches(selector, grandchild, null)); + } + + @Test + void attributePresentMatchesEvenWithEmptyValue() { + AttributeSelector selector = new AttributeSelector("style", null); + TestElement withAttr = element("Button", null, null); + withAttr.setAttribute("style", "SWT.PUSH"); + assertTrue(SelectorMatcher.matches(selector, withAttr, null)); + assertFalse(SelectorMatcher.matches(selector, element("Button", null, null), null)); + } + + @Test + void attributeIncludesIsWordBoundaryMatch() { + AttributeIncludes selector = new AttributeIncludes("style", "SWT.CHECK"); + TestElement match = element("Button", null, null); + match.setAttribute("style", "SWT.CHECK SWT.BORDER"); + assertTrue(SelectorMatcher.matches(selector, match, null)); + + TestElement substring = element("Button", null, null); + substring.setAttribute("style", "SWT.CHECK_DELAYED"); + // 'CHECK' is a substring of 'CHECK_DELAYED' but not a whitespace-separated word. + assertFalse(SelectorMatcher.matches(new AttributeIncludes("style", "CHECK"), substring, null)); + } + + @Test + void attributeBeginHyphen() { + AttributeBeginHyphen selector = new AttributeBeginHyphen("lang", "en"); + TestElement exact = element("p", null, null); + exact.setAttribute("lang", "en"); + TestElement prefixed = element("p", null, null); + prefixed.setAttribute("lang", "en-US"); + TestElement other = element("p", null, null); + other.setAttribute("lang", "fr"); + assertTrue(SelectorMatcher.matches(selector, exact, null)); + assertTrue(SelectorMatcher.matches(selector, prefixed, null)); + assertFalse(SelectorMatcher.matches(selector, other, null)); + } + + @Test + void pseudoClassMatchesViaIsPseudoInstanceOf() { + PseudoClass selector = new PseudoClass("selected"); + TestElement on = new TestElement("Button", ENGINE) { + @Override + public boolean isPseudoInstanceOf(String s) { + return "selected".equals(s); + } + }; + TestElement off = element("Button", null, null); + assertTrue(SelectorMatcher.matches(selector, on, null)); + assertFalse(SelectorMatcher.matches(selector, off, null)); + } + + @Test + void selectorListMatchesAnyAlternative() { + SelectorList list = new SelectorList(List.of(new ClassSelector("a"), new ClassSelector("b"))); + assertTrue(SelectorMatcher.matches(list, element("Button", "a", null), null)); + assertTrue(SelectorMatcher.matches(list, element("Button", "b", null), null)); + assertFalse(SelectorMatcher.matches(list, element("Button", "c", null), null)); + } + + @Test + void specificityMatchesCss21() { + assertEquals(0, new Universal().specificity()); + assertEquals(1, new ElementType("Button").specificity()); + assertEquals(10, new ClassSelector("foo").specificity()); + assertEquals(100, new IdSelector("go").specificity()); + assertEquals(11, new And(new ElementType("Button"), new ClassSelector("primary")).specificity()); + assertEquals(111, new And(new And(new ElementType("Button"), new ClassSelector("primary")), + new IdSelector("go")).specificity()); + } + + @Test + void specificityOfSelectorListIsMaxOverAlternatives() { + // "Button, .foo, #go": max specificity is the id (100). + SelectorList list = new SelectorList( + List.of(new ElementType("Button"), new ClassSelector("foo"), new IdSelector("go"))); + assertEquals(100, list.specificity()); + } + + @Test + void adjacentSiblingCombinatorRequiresSiblingSupport() { + // TestElement's ElementAdapter base returns null from getPreviousSibling, + // so adjacent matching cannot succeed against it. Locks in that the + // matcher returns false rather than throwing on elements without + // sibling support. + Adjacent selector = new Adjacent(new ElementType("Label"), new ElementType("Button")); + TestElement parent = element("Composite", null, null); + new TestElement("Label", parent, ENGINE); + TestElement second = new TestElement("Button", parent, ENGINE); + assertFalse(SelectorMatcher.matches(selector, second, null)); + } +} diff --git a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/CSSEngineTest.java b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/CSSEngineTest.java index fa16ea5395e..31916477fd6 100644 --- a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/CSSEngineTest.java +++ b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/CSSEngineTest.java @@ -23,10 +23,10 @@ import org.eclipse.e4.ui.css.core.engine.CSSEngine; import org.eclipse.e4.ui.css.core.impl.engine.CSSEngineImpl; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors.Selector; import org.eclipse.e4.ui.tests.css.core.util.TestElement; import org.junit.jupiter.api.Test; -import org.w3c.css.sac.Selector; -import org.w3c.css.sac.SelectorList; import org.w3c.dom.Element; class CSSEngineTest { @@ -55,7 +55,7 @@ private static Selector parse(CSSEngine engine, String selector) throws Exceptio @Test void testSelectorMatch() throws Exception { TestCSSEngine engine = new TestCSSEngine(); - SelectorList list = engine.parseSelectors("Date"); + Selectors.SelectorList list = engine.parseSelectors("Date"); engine.setElementProvider((element, engine1) -> new TestElement(element.getClass().getSimpleName(), engine1)); assertFalse(engine.matches(list.item(0), new Object(), null)); @@ -249,7 +249,7 @@ void testNegativeMatch() throws Exception { @Test void testSelectorListMatch() throws Exception { TestCSSEngine engine = new TestCSSEngine(); - SelectorList list = engine.parseSelectors(".a, .b"); + Selectors.SelectorList list = engine.parseSelectors(".a, .b"); TestElement a = createElement(engine, "Button", "a", null); TestElement b = createElement(engine, "Button", "b", null); TestElement c = createElement(engine, "Button", "c", null); @@ -274,9 +274,9 @@ void testTagNameCaseSensitivity() throws Exception { assertFalse(engine.matches(lower, capitalElement, null)); } - private static boolean matchesAny(CSSEngine engine, SelectorList list, Element element) { - for (int i = 0; i < list.getLength(); i++) { - if (engine.matches(list.item(i), element, null)) { + private static boolean matchesAny(CSSEngine engine, Selectors.SelectorList list, Element element) { + for (Selector selector : list.alternatives()) { + if (engine.matches(selector, element, null)) { return true; } } diff --git a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/CssCoreTestSuite.java b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/CssCoreTestSuite.java index cb33fdd3c4f..d57f435d008 100644 --- a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/CssCoreTestSuite.java +++ b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/CssCoreTestSuite.java @@ -15,7 +15,7 @@ *******************************************************************************/ package org.eclipse.e4.ui.tests.css.core; -import org.eclipse.e4.ui.css.core.impl.engine.AbstractCSSEngineTest; +import org.eclipse.e4.ui.css.core.impl.engine.CSSEngineImplTest; import org.eclipse.e4.ui.tests.css.core.dom.CSSPropertyHandlerProviderTest; import org.eclipse.e4.ui.tests.css.core.parser.CascadeTest; import org.eclipse.e4.ui.tests.css.core.parser.FontFaceRulesTest; @@ -42,7 +42,7 @@ CSSEngineTest.class, ImportTest.class, InheritTest.class, - AbstractCSSEngineTest.class, + CSSEngineImplTest.class, CSSPropertyHandlerProviderTest.class }) @Suite diff --git a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/parser/SelectorTest.java b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/parser/SelectorTest.java index f3419a83eb0..43a3be1899b 100644 --- a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/parser/SelectorTest.java +++ b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/parser/SelectorTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2013, 2014 IBM Corporation and others. + * Copyright (c) 2013, 2026 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -21,11 +21,11 @@ import java.io.IOException; import org.eclipse.e4.ui.css.core.engine.CSSEngine; +import org.eclipse.e4.ui.css.core.impl.engine.selector.Selectors; import org.eclipse.e4.ui.tests.css.core.util.ParserTestUtil; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.w3c.css.sac.CSSParseException; -import org.w3c.css.sac.SelectorList; public class SelectorTest { private CSSEngine engine; @@ -37,35 +37,37 @@ public void setUp() throws Exception { @Test void testSimpleSelector() throws Exception { - SelectorList list = engine.parseSelectors("Type1"); + Selectors.SelectorList list = engine.parseSelectors("Type1"); assertNotNull(list); assertEquals(1, list.getLength()); - assertEquals("Type1", list.item(0).toString()); + assertEquals("Type1", list.item(0).text()); } @Test void testMultipleSelectors() throws Exception { - SelectorList list = engine.parseSelectors("Type1, Type2"); + Selectors.SelectorList list = engine.parseSelectors("Type1, Type2"); assertNotNull(list); assertEquals(2, list.getLength()); - assertEquals("Type1", list.item(0).toString()); - assertEquals("Type2", list.item(1).toString()); + assertEquals("Type1", list.item(0).text()); + assertEquals("Type2", list.item(1).text()); } @Test void testClassSelector() throws Exception { - SelectorList list = engine.parseSelectors(".Class1"); + Selectors.SelectorList list = engine.parseSelectors(".Class1"); assertNotNull(list); assertEquals(1, list.getLength()); - assertEquals("*[class=\"Class1\"]", list.item(0).toString()); + assertEquals(".Class1", list.item(0).text()); } @Test void testAttributeSelector() throws Exception { - SelectorList list = engine.parseSelectors("*[class='Class1']"); + Selectors.SelectorList list = engine.parseSelectors("*[class='Class1']"); assertNotNull(list); assertEquals(1, list.getLength()); - assertEquals("*[class=\"Class1\"]", list.item(0).toString()); + // The Universal selector ('*') is folded away since the AttributeSelector + // alone carries the full match condition. + assertEquals("[class='Class1']", list.item(0).text()); } @Test diff --git a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/util/ParserTestUtil.java b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/util/ParserTestUtil.java index 21fd0fffcbc..e72b95fd186 100644 --- a/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/util/ParserTestUtil.java +++ b/tests/org.eclipse.e4.ui.tests.css.core/src/org/eclipse/e4/ui/tests/css/core/util/ParserTestUtil.java @@ -19,7 +19,7 @@ import org.eclipse.e4.ui.css.core.dom.parsers.CSSParser; import org.eclipse.e4.ui.css.core.engine.CSSEngine; import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler; -import org.eclipse.e4.ui.css.core.impl.engine.AbstractCSSEngine; +import org.eclipse.e4.ui.css.core.impl.engine.CSSEngineImpl; import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl; import org.eclipse.swt.widgets.Display; import org.w3c.css.sac.InputSource; @@ -51,7 +51,7 @@ public static CSSStyleSheet parseCss(String css) */ public static CSSStyleSheet parseCssWithoutImports(String css) throws IOException { - CSSParser parser = ((AbstractCSSEngine) createEngine()).makeCSSParser(); + CSSParser parser = ((CSSEngineImpl) createEngine()).makeCSSParser(); InputSource source = new InputSource(); source.setCharacterStream(new StringReader(css)); return parser.parseStyleSheet(source); diff --git a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/TestPropertyHelper.java b/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/TestPropertyHelper.java deleted file mode 100644 index 2b40fc72ff3..00000000000 --- a/tests/org.eclipse.e4.ui.tests.css.swt/src/org/eclipse/e4/ui/tests/css/swt/TestPropertyHelper.java +++ /dev/null @@ -1,84 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2013, 2014 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 - * Thibault Le Ouay - Bug 443094 - *******************************************************************************/ -package org.eclipse.e4.ui.tests.css.swt; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.eclipse.e4.ui.css.swt.helpers.PropertyHelper; -import org.junit.jupiter.api.Test; - -public class TestPropertyHelper { - public static class Base { - private String a = "A"; - public String getA() { - return a; - } - public void setA(String a) { - this.a = a; - } - - public String getC() { - return "C"; - } - - public boolean isD() { - return true; - } - } - - public static class Impl extends Base { - private String b = "B"; - private Base nested = new Base(); - { - nested.a = "Nested"; - } - - public String getB() { - return b; - } - - public void setB(String b) { - this.b = b; - } - - public Base getNested() { - return nested; - } - - public void setNested(Base nested) { - this.nested = nested; - } - } - - @Test - void testReadWriteProperty() throws Exception { - Impl bean = new Impl(); - assertEquals("A",PropertyHelper.getProperty(bean, "a")); - assertEquals("B",PropertyHelper.getProperty(bean, "b")); - } - - @Test - void testReadOnlyProperty() throws Exception { - Impl bean = new Impl(); - assertEquals("C",PropertyHelper.getProperty(bean, "c")); - assertEquals(true,PropertyHelper.getProperty(bean, "d")); - } - - @Test - void testNestedProperty() throws Exception { - Impl bean = new Impl(); - assertEquals("Nested",PropertyHelper.getProperty(bean, "nested.a")); - } -}