/******************************************************************************* * Copyright (c) 2008, 2015 Angelo Zerr and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Angelo Zerr <angelo.zerr@gmail.com> - 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 <Lars.Vogel@gmail.com> - Bug 428715 * Brian de Alwis (MTI) - Performance tweaks (Bug 430829) * Dirk Fauth <dirk.fauth@googlemail.com> - Bug 479896 * Patrik Suzzi <psuzzi@gmail.com> - Bug 500402 * Daniel Raap <raap@subshell.com> - 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.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; 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.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 <a href="mailto:angelo.zerr@gmail.com">Angelo ZERR</a> * */ 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 final static IResourcesLocatorManager defaultResourcesLocatorManager = ResourcesLocatorManager.INSTANCE; /** * w3c {@link DocumentCSS}. */ private ExtendedDocumentCSS documentCSS; /** * w3c {@link ViewCSS}. */ private ViewCSS viewCSS; /** * {@link IElementProvider} used to retrieve w3c Element linked to the * widget. */ private IElementProvider elementProvider; protected boolean computeDefaultStyle = false; private Map<Object, CSSElementContext> 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<ICSSPropertyHandlerProvider> propertyHandlerProviders = new ArrayList<>(); private Map<String, String> currentCSSPropertiesApplyed; private boolean throwError; private Map<Object, ICSSValueConverter> 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 { Path p = new Path(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); } } } InputStream stream = null; try { 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)); } } finally { if (stream != null) { stream.close(); } } } // 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; } /** * Return true if <code>source</code> is valid and false otherwise. * * @param source * @throws IOException */ 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) throws IOException { Reader reader = new StringReader(style); return parseStyleDeclaration(reader); } @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(); CSSStyleDeclaration styleDeclaration = parser .parseStyleDeclaration(source); return styleDeclaration; } /*--------------- Parse CSS Selector -----------------*/ @Override public SelectorList parseSelectors(String selector) throws IOException { Reader reader = new StringReader(selector); return parseSelectors(reader); } @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(); SelectorList list = parser.parseSelectors(source); return list; } /*--------------- 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) throws IOException { Reader reader = new StringReader(value); return parsePropertyValue(reader); } @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) { if (!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) { // 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(element, styleWithPseudoInstance, // pseudoInstance); applyStyleDeclaration(elt, styleWithPseudoInstance, pseudoInstance); } } } } if (style != null) { //applyStyleDeclaration(element, 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 ? ((ChildVisibilityAwareElement) elt).getVisibleChildNodes() : elt.getChildNodes(); if (nodes != null) { for (int k = 0; k < nodes.getLength(); k++) { applyStyles(nodes.item(k), 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. * * @param elt * @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) { for (int i = 0; i < l.getLength(); 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) { CSSStylableElement stylableElement = (CSSStylableElement) element; return stylableElement.getStaticPseudoInstances(); } return null; } /** * Callback method called when styles applied of <code>nodes</code> * children of the <code>element</code>. * * @param element * @param nodes */ 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 = currentCSSPropertiesApplyed == null; if (avoidanceCacheInstalled) { currentCSSPropertiesApplyed = new HashMap<>(); } List<ICSSPropertyHandler2> handlers2 = null; 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) { if (handlers2 == null) { handlers2 = new ArrayList<>(); } if (!handlers2.contains(propertyHandler2)) { handlers2.add(propertyHandler2); } } } catch (Exception e) { if (throwError || (!throwError && !(e instanceof UnsupportedPropertyException))) { handleExceptions(e); } } } if (handlers2 != null) { for (ICSSPropertyHandler2 handler2 : handlers2) { try { handler2.onAllCSSPropertiesApplyed(element, this, pseudo); } catch (Exception e) { handleExceptions(e); } } } if (avoidanceCacheInstalled) { currentCSSPropertiesApplyed = 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) throws IOException { Element elt = getElement(node); if (elt != null) { if (elt instanceof CSSStylableElement) { CSSStylableElement stylableElement = (CSSStylableElement) elt; String style = stylableElement.getCSSStyle(); if (style != null && style.length() > 0) { parseAndApplyStyleDeclaration(stylableElement .getNativeWidget(), style); } } if (applyStylesToChildNodes) { /* * Style all children recursive. */ NodeList nodes = elt.getChildNodes(); if (nodes != null) { for (int k = 0; k < nodes.getLength(); k++) { applyInlineStyle(nodes.item(k), 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) { CSSStylableElement stylableElement = (CSSStylableElement) elt; CSSStyleDeclaration oldDefaultStyleDeclaration = stylableElement .getDefaultStyleDeclaration(pseudoE); // CSSStyleDeclaration defaultStyleDeclaration = // computeDefaultStyleDeclaration( // stylableElement, newStyle); 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) { for (int k = 0; k < nodes.getLength(); k++) { applyDefaultStyleDeclaration(nodes.item(k), applyStylesToChildNodes); } onStylesAppliedToChildNodes(elt, nodes); } } } } /** * Delegates the handle method. * * @param element * may be a widget or a node or some object * @param property * @param value * @param pseudo */ @Override public ICSSPropertyHandler applyCSSProperty(Object element, String property, CSSValue value, String pseudo) throws Exception { if (currentCSSPropertiesApplyed != null && currentCSSPropertiesApplyed.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<ICSSPropertyHandler> 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 (currentCSSPropertiesApplyed != null) { currentCSSPropertiesApplyed.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<ICSSPropertyHandler> 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<ICSSPropertyHandler> handlers = getCSSPropertyHandlers(property); if (handlers == null) { return null; } for (ICSSPropertyHandler handler : handlers) { if (handler instanceof ICSSPropertyCompositeHandler) { ICSSPropertyCompositeHandler compositeHandler = (ICSSPropertyCompositeHandler) handler; if (compositeHandler.isCSSPropertyComposite(property)) { return compositeHandler.getCSSPropertiesNames(property); } } } } catch (Exception e) { handleExceptions(e); } return null; } protected Collection<ICSSPropertyHandler> getCSSPropertyHandlers( String property) throws Exception { Collection<ICSSPropertyHandler> handlers = new ArrayList<>(); for (ICSSPropertyHandlerProvider provider : propertyHandlerProviders) { Collection<ICSSPropertyHandler> 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. * * @param node * @return the property names and handlers */ @Override public Collection<String> getCSSProperties(Object element) { Set<String> 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; // this.elementsContext = null; } /** * Return the w3c Element linked to the Object element. * * @param element * @return */ @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. * <p> * The default implementation of this method does nothing. * </p> * * @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. */ protected void handleWidgetDisposed(Object widget) { if (elementsContext != null) { elementsContext.remove(widget); } } public Object getDocument() { return null; } @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<Object, CSSElementContext> 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 extendedSelector = (ExtendedSelector) selector; 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<CSSElementContext> 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 { 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; } }