123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890 |
- /*
- * Copyright 2000-2021 Vaadin Ltd.
- *
- * 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.
- */
- package com.vaadin.ui.declarative;
-
- import java.io.Serializable;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.concurrent.ConcurrentHashMap;
-
- import org.jsoup.nodes.Attributes;
- import org.jsoup.nodes.Document;
- import org.jsoup.nodes.Element;
- import org.jsoup.nodes.Node;
-
- import com.vaadin.annotations.DesignRoot;
- import com.vaadin.server.Constants;
- import com.vaadin.server.DeploymentConfiguration;
- import com.vaadin.server.VaadinService;
- import com.vaadin.shared.Registration;
- import com.vaadin.ui.Component;
- import com.vaadin.ui.HasComponents;
- import com.vaadin.ui.declarative.Design.ComponentFactory;
- import com.vaadin.ui.declarative.Design.ComponentMapper;
-
- /**
- * This class contains contextual information that is collected when a component
- * tree is constructed based on HTML design template. This information includes
- * mappings from local ids, global ids and captions to components , as well as a
- * mapping between prefixes and package names (such as "vaadin" ->
- * "com.vaadin.ui").
- *
- * Versions prior to 7.6 use "v" as the default prefix. Versions starting with
- * 7.6 support reading designs with either "v" or "vaadin" as the prefix, but
- * only write "vaadin" by default. Writing with the legacy prefix can be
- * activated with the property or context parameter
- * {@link Constants#SERVLET_PARAMETER_LEGACY_DESIGN_PREFIX}.
- *
- * @since 7.4
- * @author Vaadin Ltd
- */
- public class DesignContext implements Serializable {
-
- private static final String LEGACY_PREFIX = "v";
- private static final String VAADIN_PREFIX = "vaadin";
- private static final String VAADIN7_PREFIX = "vaadin7";
-
- private static final String VAADIN_UI_PACKAGE = "com.vaadin.ui";
- private static final String VAADIN7_UI_PACKAGE = "com.vaadin.v7.ui";
-
- // cache for object instances
- private static Map<Class<?>, Component> instanceCache = new ConcurrentHashMap<>();
-
- // The root component of the component hierarchy
- private Component rootComponent = null;
- // Attribute names for global id and caption and the prefix name for a local
- // id
- public static final String ID_ATTRIBUTE = "id";
- public static final String CAPTION_ATTRIBUTE = "caption";
- public static final String LOCAL_ID_ATTRIBUTE = "_id";
- // Mappings from ids to components. Modified when reading from design.
- private Map<String, Component> idToComponent = new HashMap<>();
- private Map<String, Component> localIdToComponent = new HashMap<>();
- private Map<String, Component> captionToComponent = new HashMap<>();
- // Mapping from components to local ids. Accessed when writing to
- // design. Modified when reading from design.
- private Map<Component, String> componentToLocalId = new HashMap<>();
- private Document doc; // required for calling createElement(String)
- // namespace mappings
- private Map<String, String> packageToPrefix = new HashMap<>();
- private Map<String, String> prefixToPackage = new HashMap<>();
- private final Map<Component, Map<String, String>> customAttributes = new HashMap<>();
-
- // component creation listeners
- private List<ComponentCreationListener> listeners = new ArrayList<>();
-
- private ShouldWriteDataDelegate shouldWriteDataDelegate = ShouldWriteDataDelegate.DEFAULT;
-
- // this cannot be static because of testability issues
- private Boolean legacyDesignPrefix = null;
-
- private boolean shouldWriteDefaultValues = false;
-
- public DesignContext(Document doc) {
- this.doc = doc;
- // Initialize the mapping between prefixes and package names.
- if (isLegacyPrefixEnabled()) {
- addPackagePrefix(LEGACY_PREFIX, VAADIN_UI_PACKAGE);
- prefixToPackage.put(VAADIN_PREFIX, VAADIN_UI_PACKAGE);
- } else {
- addPackagePrefix(VAADIN_PREFIX, VAADIN_UI_PACKAGE);
- prefixToPackage.put(LEGACY_PREFIX, VAADIN_UI_PACKAGE);
- }
- addPackagePrefix(VAADIN7_PREFIX, VAADIN7_UI_PACKAGE);
- }
-
- public DesignContext() {
- this(new Document(""));
- }
-
- /**
- * Returns a component having the specified local id. If no component is
- * found, returns null.
- *
- * @param localId
- * The local id of the component
- * @return a component whose local id equals localId
- */
- public Component getComponentByLocalId(String localId) {
- return localIdToComponent.get(localId);
- }
-
- /**
- * Returns a component having the specified global id. If no component is
- * found, returns null.
- *
- * @param globalId
- * The global id of the component
- * @return a component whose global id equals globalId
- */
- public Component getComponentById(String globalId) {
- return idToComponent.get(globalId);
- }
-
- /**
- * Returns a component having the specified caption. If no component is
- * found, returns null.
- *
- * @param caption
- * The caption of the component
- * @return a component whose caption equals the caption given as a parameter
- */
- public Component getComponentByCaption(String caption) {
- return captionToComponent.get(caption);
- }
-
- /**
- * Creates a mapping between the given global id and the component. Returns
- * true if globalId was already mapped to some component. Otherwise returns
- * false. Also sets the id of the component to globalId.
- *
- * If there is a mapping from the component to a global id (gid) different
- * from globalId, the mapping from gid to component is removed.
- *
- * If the string was mapped to a component c different from the given
- * component, the mapping from c to the string is removed. Similarly, if
- * component was mapped to some string s different from globalId, the
- * mapping from s to component is removed.
- *
- * @param globalId
- * The new global id of the component.
- * @param component
- * The component whose global id is to be set.
- * @return true, if there already was a global id mapping from the string to
- * some component.
- */
- private boolean mapId(String globalId, Component component) {
- Component oldComponent = idToComponent.get(globalId);
- if (oldComponent != null && !oldComponent.equals(component)) {
- oldComponent.setId(null);
- }
- String oldGID = component.getId();
- if (oldGID != null && !oldGID.equals(globalId)) {
- idToComponent.remove(oldGID);
- }
- component.setId(globalId);
- idToComponent.put(globalId, component);
- return oldComponent != null && !oldComponent.equals(component);
- }
-
- /**
- * Creates a mapping between the given local id and the component. Returns
- * true if localId was already mapped to some component or if component was
- * mapped to some string. Otherwise returns false.
- *
- * If the string was mapped to a component c different from the given
- * component, the mapping from c to the string is removed. Similarly, if
- * component was mapped to some string s different from localId, the mapping
- * from s to component is removed.
- *
- * @since 7.5.0
- *
- * @param component
- * The component whose local id is to be set.
- * @param localId
- * The new local id of the component.
- *
- * @return true, if there already was a local id mapping from the string to
- * some component or from the component to some string. Otherwise
- * returns false.
- */
- public boolean setComponentLocalId(Component component, String localId) {
- return twoWayMap(localId, component, localIdToComponent,
- componentToLocalId);
- }
-
- /**
- * Returns the local id for a component.
- *
- * @since 7.5.0
- *
- * @param component
- * The component whose local id to get.
- * @return the local id of the component, or null if the component has no
- * local id assigned
- */
- public String getComponentLocalId(Component component) {
- return componentToLocalId.get(component);
- }
-
- /**
- * Creates a mapping between the given caption and the component. Returns
- * true if caption was already mapped to some component.
- *
- * Note that unlike mapGlobalId, if some component already has the given
- * caption, the caption is not cleared from the component. This allows
- * non-unique captions. However, only one of the components corresponding to
- * a given caption can be found using the map captionToComponent. Hence, any
- * captions that are used to identify an object should be unique.
- *
- * @param caption
- * The new caption of the component.
- * @param component
- * The component whose caption is to be set.
- * @return true, if there already was a caption mapping from the string to
- * some component.
- */
- private boolean mapCaption(String caption, Component component) {
- return captionToComponent.put(caption, component) != null;
- }
-
- /**
- * Creates a two-way mapping between key and value, i.e. adds key -> value
- * to keyToValue and value -> key to valueToKey. If key was mapped to a
- * value v different from the given value, the mapping from v to key is
- * removed. Similarly, if value was mapped to some key k different from key,
- * the mapping from k to value is removed.
- *
- * Returns true if there already was a mapping from key to some value v or
- * if there was a mapping from value to some key k. Otherwise returns false.
- *
- * @param key
- * The new key in keyToValue.
- * @param value
- * The new value in keyToValue.
- * @param keyToValue
- * A map from keys to values.
- * @param valueToKey
- * A map from values to keys.
- * @return whether there already was some mapping from key to a value or
- * from value to a key.
- */
- private <S, T> boolean twoWayMap(S key, T value, Map<S, T> keyToValue,
- Map<T, S> valueToKey) {
- T oldValue = keyToValue.put(key, value);
- if (oldValue != null && !oldValue.equals(value)) {
- valueToKey.remove(oldValue);
- }
- S oldKey = valueToKey.put(value, key);
- if (oldKey != null && !oldKey.equals(key)) {
- keyToValue.remove(oldKey);
- }
- return oldValue != null || oldKey != null;
- }
-
- /**
- * Creates a two-way mapping between a prefix and a package name.
- *
- * Note that modifying the mapping for {@value #VAADIN_UI_PACKAGE} may
- * invalidate the backwards compatibility mechanism supporting reading such
- * components with either {@value #LEGACY_PREFIX} or {@value #VAADIN_PREFIX}
- * as prefix.
- *
- * @param prefix
- * the prefix name without an ending dash (for instance, "vaadin"
- * is by default used for "com.vaadin.ui")
- * @param packageName
- * the name of the package corresponding to prefix
- *
- * @see #getPackagePrefixes()
- * @see #getPackagePrefix(String)
- * @see #getPackage(String)
- * @since 7.5.0
- */
- public void addPackagePrefix(String prefix, String packageName) {
- twoWayMap(prefix, packageName, prefixToPackage, packageToPrefix);
- }
-
- /**
- * Gets the prefix mapping for a given package, or <code>null</code> if
- * there is no mapping for the package.
- *
- * @see #addPackagePrefix(String, String)
- * @see #getPackagePrefixes()
- *
- * @since 7.5.0
- * @param packageName
- * the package name to get a prefix for
- * @return the prefix for the package, or <code>null</code> if no prefix is
- * registered
- */
- public String getPackagePrefix(String packageName) {
- if (VAADIN_UI_PACKAGE.equals(packageName)) {
- return isLegacyPrefixEnabled() ? LEGACY_PREFIX : VAADIN_PREFIX;
- } else {
- return packageToPrefix.get(packageName);
- }
- }
-
- /**
- * Gets all registered package prefixes.
- *
- *
- * @since 7.5.0
- * @see #getPackage(String)
- * @return a collection of package prefixes
- */
- public Collection<String> getPackagePrefixes() {
- return Collections.unmodifiableCollection(prefixToPackage.keySet());
- }
-
- /**
- * Gets the package corresponding to the give prefix, or <code>null</code>
- * no package has been registered for the prefix.
- *
- * @since 7.5.0
- * @see #addPackagePrefix(String, String)
- * @param prefix
- * the prefix to find a package for
- * @return the package prefix, or <code>null</code> if no package is
- * registered for the provided prefix
- */
- public String getPackage(String prefix) {
- return prefixToPackage.get(prefix);
- }
-
- /**
- * Returns the default instance for the given class. The instance must not
- * be modified by the caller.
- *
- * @param <T>
- * a component class
- * @param component
- * the component that determines the class
- * @return the default instance for the given class. The return value must
- * not be modified by the caller
- */
- @SuppressWarnings("unchecked")
- public <T> T getDefaultInstance(Component component) {
- // If the root is a @DesignRoot component, it can't use itself as a
- // reference or the written design will be empty
-
- // If the root component in some other way initializes itself in the
- // constructor
- if (getRootComponent() == component
- && component.getClass().isAnnotationPresent(DesignRoot.class)) {
- return (T) getDefaultInstance((Class<? extends Component>) component
- .getClass().getSuperclass());
- }
- return (T) getDefaultInstance(component.getClass());
- }
-
- private Component getDefaultInstance(
- Class<? extends Component> componentClass) {
- Component instance = instanceCache.get(componentClass);
- if (instance == null) {
- instance = instantiateClass(componentClass.getName());
- instanceCache.put(componentClass, instance);
- }
- return instance;
- }
-
- /**
- * Reads and stores the mappings from prefixes to package names from meta
- * tags located under <head> in the html document.
- *
- * @param doc
- * the document
- */
- protected void readPackageMappings(Document doc) {
- Element head = doc.head();
- if (head == null) {
- return;
- }
- for (Node child : head.childNodes()) {
- if (child instanceof Element) {
- Element childElement = (Element) child;
- if ("meta".equals(childElement.tagName())) {
- Attributes attributes = childElement.attributes();
- if (attributes.hasKey("name")
- && attributes.hasKey("content") && "package-mapping"
- .equals(attributes.get("name"))) {
- String contentString = attributes.get("content");
- String[] parts = contentString.split(":");
- if (parts.length != 2) {
- throw new DesignException("The meta tag '" + child
- + "' cannot be parsed.");
- }
- String prefixName = parts[0];
- String packageName = parts[1];
- addPackagePrefix(prefixName, packageName);
- }
- }
- }
- }
- }
-
- /**
- * Writes the package mappings (prefix -> package name) of this object to
- * the specified document.
- * <p>
- * The prefixes are stored as <meta> tags under <head> in the document.
- *
- * @param doc
- * the Jsoup document tree where the package mappings are written
- */
- public void writePackageMappings(Document doc) {
- Element head = doc.head();
- for (String prefix : getPackagePrefixes()) {
- // Only store the prefix-name mapping if it is not a default mapping
- // (such as "vaadin" -> "com.vaadin.ui")
- if (!VAADIN_PREFIX.equals(prefix) && !VAADIN7_PREFIX.equals(prefix)
- && !LEGACY_PREFIX.equals(prefix)) {
- Node newNode = doc.createElement("meta");
- newNode.attr("name", "package-mapping");
- String prefixToPackageName = prefix + ":" + getPackage(prefix);
- newNode.attr("content", prefixToPackageName);
- head.appendChild(newNode);
- }
- }
- }
-
- /**
- * Check whether the legacy prefix "v" or the default prefix "vaadin" should
- * be used when writing designs. The property or context parameter
- * {@link Constants#SERVLET_PARAMETER_LEGACY_DESIGN_PREFIX} can be used to
- * switch to the legacy prefix.
- *
- * @since 7.5.7
- * @return true to use the legacy prefix, false by default
- */
- protected boolean isLegacyPrefixEnabled() {
- if (legacyDesignPrefix != null) {
- return legacyDesignPrefix.booleanValue();
- }
- if (VaadinService.getCurrent() == null) {
- // This will happen at least in JUnit tests.
- return false;
- }
- DeploymentConfiguration configuration = VaadinService.getCurrent()
- .getDeploymentConfiguration();
- legacyDesignPrefix = configuration.getApplicationOrSystemProperty(
- Constants.SERVLET_PARAMETER_LEGACY_DESIGN_PREFIX, "false")
- .equals("true");
- return legacyDesignPrefix.booleanValue();
- }
-
- /**
- * Creates an html tree node corresponding to the given element. Also
- * initializes its attributes by calling writeDesign. As a result of the
- * writeDesign() call, this method creates the entire subtree rooted at the
- * returned Node.
- *
- * @param childComponent
- * The component with state that is written in to the node
- * @return An html tree node corresponding to the given component. The tag
- * name of the created node is derived from the class name of
- * childComponent.
- */
- public Element createElement(Component childComponent) {
- ComponentMapper componentMapper = Design.getComponentMapper();
-
- String tagName = componentMapper.componentToTag(childComponent, this);
-
- Element newElement = doc.createElement(tagName);
- childComponent.writeDesign(newElement, this);
- // Handle the local id. Global id and caption should have been taken
- // care of by writeDesign.
- String localId = componentToLocalId.get(childComponent);
- if (localId != null) {
- newElement.attr(LOCAL_ID_ATTRIBUTE, localId);
- }
- return newElement;
- }
-
- /**
- * Reads the given design node and creates the corresponding component tree.
- *
- * @param componentDesign
- * The design element containing the description of the component
- * to be created.
- * @return the root component of component tree
- */
- public Component readDesign(Element componentDesign) {
- // Create the component.
- Component component = instantiateComponent(componentDesign);
- readDesign(componentDesign, component);
- fireComponentCreatedEvent(componentToLocalId.get(component), component);
- return component;
- }
-
- /**
- *
- * Reads the given design node and populates the given component with the
- * corresponding component tree.
- * <p>
- * Additionally registers the component id, local id and caption of the
- * given component and all its children in the context
- *
- * @param componentDesign
- * The design element containing the description of the component
- * to be created
- * @param component
- * The component which corresponds to the design element
- */
- public void readDesign(Element componentDesign, Component component) {
- component.readDesign(componentDesign, this);
- // Get the ids and the caption of the component and store them in the
- // maps of this design context.
- org.jsoup.nodes.Attributes attributes = componentDesign.attributes();
- // global id: only update the mapping, the id has already been set for
- // the component
- String id = component.getId();
- if (id != null && !id.isEmpty()) {
- boolean mappingExists = mapId(id, component);
- if (mappingExists) {
- throw new DesignException(
- "The following global id is not unique: " + id);
- }
- }
- // local id: this is not a property of a component, so need to fetch it
- // from the attributes of componentDesign
- if (attributes.hasKey(LOCAL_ID_ATTRIBUTE)) {
- String localId = attributes.get(LOCAL_ID_ATTRIBUTE);
- boolean mappingExists = setComponentLocalId(component, localId);
- if (mappingExists) {
- throw new DesignException(
- "the following local id is not unique: " + localId);
- }
- }
- // caption: a property of a component, possibly not unique
- String caption = component.getCaption();
- if (caption != null) {
- mapCaption(caption, component);
- }
- }
-
- /**
- * Creates a Component corresponding to the given node. Does not set the
- * attributes for the created object.
- *
- * @param node
- * a node of an html tree
- * @return a Component corresponding to node, with no attributes set.
- */
- private Component instantiateComponent(Node node) {
- String tag = node.nodeName();
-
- ComponentMapper componentMapper = Design.getComponentMapper();
- Component component = componentMapper.tagToComponent(tag,
- Design.getComponentFactory(), this);
-
- assert tagEquals(tag, componentMapper.componentToTag(component, this));
-
- return component;
- }
-
- private boolean tagEquals(String tag1, String tag2) {
- return tag1.equals(tag2)
- || (hasVaadinPrefix(tag1) && hasVaadinPrefix(tag2));
- }
-
- private boolean hasVaadinPrefix(String tag) {
- return tag.startsWith(LEGACY_PREFIX + "-")
- || tag.startsWith(VAADIN_PREFIX + "-");
- }
-
- /**
- * Instantiates given class via ComponentFactory.
- *
- * @param qualifiedClassName
- * class name to instantiate
- * @return instance of a given class
- */
- private Component instantiateClass(String qualifiedClassName) {
- ComponentFactory factory = Design.getComponentFactory();
- Component component = factory.createComponent(qualifiedClassName, this);
-
- if (component == null) {
- throw new DesignException("Got unexpected null component from "
- + factory.getClass().getName() + " for class "
- + qualifiedClassName);
- }
-
- return component;
- }
-
- /**
- * Returns the root component of a created component hierarchy.
- *
- * @return the root component of the hierarchy
- */
- public Component getRootComponent() {
- return rootComponent;
- }
-
- /**
- * Sets the root component of a created component hierarchy.
- *
- * @param rootComponent
- * the root component of the hierarchy
- */
- public void setRootComponent(Component rootComponent) {
- this.rootComponent = rootComponent;
- }
-
- /**
- * Adds a component creation listener. The listener will be notified when
- * components are created while parsing a design template
- *
- * @param listener
- * the component creation listener to be added
- * @return a registration object for removing the listener
- */
- public Registration addComponentCreationListener(
- ComponentCreationListener listener) {
- listeners.add(listener);
- return () -> listeners.remove(listener);
- }
-
- /**
- * Removes a component creation listener.
- *
- * @param listener
- * the component creation listener to be removed
- * @deprecated Use a {@link Registration} object returned by
- * {@link #addComponentCreationListener(ComponentCreationListener)}
- * a listener
- */
- @Deprecated
- public void removeComponentCreationListener(
- ComponentCreationListener listener) {
- listeners.remove(listener);
- }
-
- /**
- * Fires component creation event
- *
- * @param localId
- * localId of the component
- * @param component
- * the component that was created
- */
- private void fireComponentCreatedEvent(String localId,
- Component component) {
- ComponentCreatedEvent event = new ComponentCreatedEvent(localId,
- component);
- for (ComponentCreationListener listener : listeners) {
- listener.componentCreated(event);
- }
- }
-
- /**
- * Interface to be implemented by component creation listeners.
- *
- * @author Vaadin Ltd
- */
- @FunctionalInterface
- public interface ComponentCreationListener extends Serializable {
-
- /**
- * Called when component has been created in the design context.
- *
- * @param event
- * the component creation event containing information on the
- * created component
- */
- public void componentCreated(ComponentCreatedEvent event);
- }
-
- /**
- * Component creation event that is fired when a component is created in
- * the. context
- *
- * @author Vaadin Ltd
- */
- public class ComponentCreatedEvent implements Serializable {
- private final String localId;
- private final Component component;
- private final DesignContext context;
-
- /**
- * Creates a new instance of ComponentCreatedEvent
- *
- * @param localId
- * the local id of the created component
- * @param component
- * the created component
- */
- private ComponentCreatedEvent(String localId, Component component) {
- this.localId = localId;
- this.component = component;
- context = DesignContext.this;
- }
-
- /**
- * Returns the local id of the created component or null if not exist.
- *
- * @return the localId
- */
- public String getLocalId() {
- return localId;
- }
-
- /**
- * Returns the created component.
- *
- * @return the component
- */
- public Component getComponent() {
- return component;
- }
-
- /**
- * Returns the new component context.
- *
- * @return the context
- *
- * @since 8.5
- */
- public DesignContext getContext() {
- return context;
- }
- }
-
- /**
- * Helper method for component write implementors to determine whether their
- * children should be written out or not.
- *
- * @param c
- * The component being written
- * @param defaultC
- * The default instance for the component
- * @return whether the children of c should be written
- */
- public boolean shouldWriteChildren(Component c, Component defaultC) {
- if (c == getRootComponent()) {
- // The root component should always write its children - otherwise
- // the result is empty
- return true;
- }
-
- if (defaultC instanceof HasComponents
- && ((HasComponents) defaultC).iterator().hasNext()) {
- // Easy version which assumes that this is a custom component if the
- // constructor adds children
- return false;
- }
-
- return true;
- }
-
- /**
- * Determines whether the container data of a component should be written
- * out by delegating to a {@link ShouldWriteDataDelegate}. The default
- * delegate assumes that all component data is provided by a data provider
- * connected to a back end system and that the data should thus not be
- * written.
- *
- * @since 7.5.0
- * @see #setShouldWriteDataDelegate(ShouldWriteDataDelegate)
- * @param component
- * the component to check
- * @return <code>true</code> if container data should be written out for the
- * provided component; otherwise <code>false</code>.
- */
- public boolean shouldWriteData(Component component) {
- return getShouldWriteDataDelegate().shouldWriteData(component);
- }
-
- /**
- * Sets the delegate that determines whether the container data of a
- * component should be written out.
- *
- * @since 7.5.0
- * @see #shouldWriteChildren(Component, Component)
- * @see #getShouldWriteDataDelegate()
- * @param shouldWriteDataDelegate
- * the delegate to set, not <code>null</code>
- * @throws IllegalArgumentException
- * if the provided delegate is <code>null</code>
- */
- public void setShouldWriteDataDelegate(
- ShouldWriteDataDelegate shouldWriteDataDelegate) {
- if (shouldWriteDataDelegate == null) {
- throw new IllegalArgumentException("Delegate cannot be null");
- }
- this.shouldWriteDataDelegate = shouldWriteDataDelegate;
- }
-
- /**
- * Gets the delegate that determines whether the container data of a
- * component should be written out.
- *
- * @since 7.5.0
- * @see #setShouldWriteDataDelegate(ShouldWriteDataDelegate)
- * @see #shouldWriteChildren(Component, Component)
- * @return the shouldWriteDataDelegate the currently use delegate
- */
- public ShouldWriteDataDelegate getShouldWriteDataDelegate() {
- return shouldWriteDataDelegate;
- }
-
- /**
- * Gets the attributes that the component did not handle.
- *
- * @since 7.7
- * @param component
- * the component to get the attributes for
- * @return map of the attributes which were not recognized by the component
- */
- public Map<String, String> getCustomAttributes(Component component) {
- return customAttributes.get(component);
- }
-
- /**
- * Sets a custom attribute not handled by the component. These attributes
- * are directly written to the component tag.
- *
- * @since 7.7
- * @param component
- * the component to set the attribute for
- * @param attribute
- * the attribute to set
- * @param value
- * the value of the attribute
- */
- public void setCustomAttribute(Component component, String attribute,
- String value) {
- Map<String, String> map = customAttributes.get(component);
- if (map == null) {
- map = new HashMap<>();
- customAttributes.put(component, map);
- }
- map.put(attribute, value);
- }
-
- /**
- * Set whether default attribute values should be written by the
- * {@code DesignAttributeHandler#writeAttribute(String, Attributes, Object, Object, Class, DesignContext)}
- * method. Default is {@code false}.
- *
- * @since 8.0
- * @param value
- * {@code true} to write default values of attributes,
- * {@code false} to disable writing of default values
- */
- public void setShouldWriteDefaultValues(boolean value) {
- shouldWriteDefaultValues = value;
- }
-
- /**
- * Determines whether default attribute values should be written by the
- * {@code DesignAttributeHandler#writeAttribute(String, Attributes, Object, Object, Class, DesignContext)}
- * method. Default is {@code false}.
- *
- * @since 8.0
- * @return {@code true} if default values of attributes should be written,
- * otherwise {@code false}
- */
- public boolean shouldWriteDefaultValues() {
- return shouldWriteDefaultValues;
- }
- }
|