You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DesignContext.java 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. /*
  2. * Copyright 2000-2021 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui.declarative;
  17. import java.io.Serializable;
  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.HashMap;
  22. import java.util.List;
  23. import java.util.Map;
  24. import java.util.concurrent.ConcurrentHashMap;
  25. import org.jsoup.nodes.Attributes;
  26. import org.jsoup.nodes.Document;
  27. import org.jsoup.nodes.Element;
  28. import org.jsoup.nodes.Node;
  29. import com.vaadin.annotations.DesignRoot;
  30. import com.vaadin.server.Constants;
  31. import com.vaadin.server.DeploymentConfiguration;
  32. import com.vaadin.server.VaadinService;
  33. import com.vaadin.shared.Registration;
  34. import com.vaadin.ui.Component;
  35. import com.vaadin.ui.HasComponents;
  36. import com.vaadin.ui.declarative.Design.ComponentFactory;
  37. import com.vaadin.ui.declarative.Design.ComponentMapper;
  38. /**
  39. * This class contains contextual information that is collected when a component
  40. * tree is constructed based on HTML design template. This information includes
  41. * mappings from local ids, global ids and captions to components , as well as a
  42. * mapping between prefixes and package names (such as "vaadin" ->
  43. * "com.vaadin.ui").
  44. *
  45. * Versions prior to 7.6 use "v" as the default prefix. Versions starting with
  46. * 7.6 support reading designs with either "v" or "vaadin" as the prefix, but
  47. * only write "vaadin" by default. Writing with the legacy prefix can be
  48. * activated with the property or context parameter
  49. * {@link Constants#SERVLET_PARAMETER_LEGACY_DESIGN_PREFIX}.
  50. *
  51. * @since 7.4
  52. * @author Vaadin Ltd
  53. */
  54. public class DesignContext implements Serializable {
  55. private static final String LEGACY_PREFIX = "v";
  56. private static final String VAADIN_PREFIX = "vaadin";
  57. private static final String VAADIN7_PREFIX = "vaadin7";
  58. private static final String VAADIN_UI_PACKAGE = "com.vaadin.ui";
  59. private static final String VAADIN7_UI_PACKAGE = "com.vaadin.v7.ui";
  60. // cache for object instances
  61. private static Map<Class<?>, Component> instanceCache = new ConcurrentHashMap<>();
  62. // The root component of the component hierarchy
  63. private Component rootComponent = null;
  64. // Attribute names for global id and caption and the prefix name for a local
  65. // id
  66. public static final String ID_ATTRIBUTE = "id";
  67. public static final String CAPTION_ATTRIBUTE = "caption";
  68. public static final String LOCAL_ID_ATTRIBUTE = "_id";
  69. // Mappings from ids to components. Modified when reading from design.
  70. private Map<String, Component> idToComponent = new HashMap<>();
  71. private Map<String, Component> localIdToComponent = new HashMap<>();
  72. private Map<String, Component> captionToComponent = new HashMap<>();
  73. // Mapping from components to local ids. Accessed when writing to
  74. // design. Modified when reading from design.
  75. private Map<Component, String> componentToLocalId = new HashMap<>();
  76. private Document doc; // required for calling createElement(String)
  77. // namespace mappings
  78. private Map<String, String> packageToPrefix = new HashMap<>();
  79. private Map<String, String> prefixToPackage = new HashMap<>();
  80. private final Map<Component, Map<String, String>> customAttributes = new HashMap<>();
  81. // component creation listeners
  82. private List<ComponentCreationListener> listeners = new ArrayList<>();
  83. private ShouldWriteDataDelegate shouldWriteDataDelegate = ShouldWriteDataDelegate.DEFAULT;
  84. // this cannot be static because of testability issues
  85. private Boolean legacyDesignPrefix = null;
  86. private boolean shouldWriteDefaultValues = false;
  87. public DesignContext(Document doc) {
  88. this.doc = doc;
  89. // Initialize the mapping between prefixes and package names.
  90. if (isLegacyPrefixEnabled()) {
  91. addPackagePrefix(LEGACY_PREFIX, VAADIN_UI_PACKAGE);
  92. prefixToPackage.put(VAADIN_PREFIX, VAADIN_UI_PACKAGE);
  93. } else {
  94. addPackagePrefix(VAADIN_PREFIX, VAADIN_UI_PACKAGE);
  95. prefixToPackage.put(LEGACY_PREFIX, VAADIN_UI_PACKAGE);
  96. }
  97. addPackagePrefix(VAADIN7_PREFIX, VAADIN7_UI_PACKAGE);
  98. }
  99. public DesignContext() {
  100. this(new Document(""));
  101. }
  102. /**
  103. * Returns a component having the specified local id. If no component is
  104. * found, returns null.
  105. *
  106. * @param localId
  107. * The local id of the component
  108. * @return a component whose local id equals localId
  109. */
  110. public Component getComponentByLocalId(String localId) {
  111. return localIdToComponent.get(localId);
  112. }
  113. /**
  114. * Returns a component having the specified global id. If no component is
  115. * found, returns null.
  116. *
  117. * @param globalId
  118. * The global id of the component
  119. * @return a component whose global id equals globalId
  120. */
  121. public Component getComponentById(String globalId) {
  122. return idToComponent.get(globalId);
  123. }
  124. /**
  125. * Returns a component having the specified caption. If no component is
  126. * found, returns null.
  127. *
  128. * @param caption
  129. * The caption of the component
  130. * @return a component whose caption equals the caption given as a parameter
  131. */
  132. public Component getComponentByCaption(String caption) {
  133. return captionToComponent.get(caption);
  134. }
  135. /**
  136. * Creates a mapping between the given global id and the component. Returns
  137. * true if globalId was already mapped to some component. Otherwise returns
  138. * false. Also sets the id of the component to globalId.
  139. *
  140. * If there is a mapping from the component to a global id (gid) different
  141. * from globalId, the mapping from gid to component is removed.
  142. *
  143. * If the string was mapped to a component c different from the given
  144. * component, the mapping from c to the string is removed. Similarly, if
  145. * component was mapped to some string s different from globalId, the
  146. * mapping from s to component is removed.
  147. *
  148. * @param globalId
  149. * The new global id of the component.
  150. * @param component
  151. * The component whose global id is to be set.
  152. * @return true, if there already was a global id mapping from the string to
  153. * some component.
  154. */
  155. private boolean mapId(String globalId, Component component) {
  156. Component oldComponent = idToComponent.get(globalId);
  157. if (oldComponent != null && !oldComponent.equals(component)) {
  158. oldComponent.setId(null);
  159. }
  160. String oldGID = component.getId();
  161. if (oldGID != null && !oldGID.equals(globalId)) {
  162. idToComponent.remove(oldGID);
  163. }
  164. component.setId(globalId);
  165. idToComponent.put(globalId, component);
  166. return oldComponent != null && !oldComponent.equals(component);
  167. }
  168. /**
  169. * Creates a mapping between the given local id and the component. Returns
  170. * true if localId was already mapped to some component or if component was
  171. * mapped to some string. Otherwise returns false.
  172. *
  173. * If the string was mapped to a component c different from the given
  174. * component, the mapping from c to the string is removed. Similarly, if
  175. * component was mapped to some string s different from localId, the mapping
  176. * from s to component is removed.
  177. *
  178. * @since 7.5.0
  179. *
  180. * @param component
  181. * The component whose local id is to be set.
  182. * @param localId
  183. * The new local id of the component.
  184. *
  185. * @return true, if there already was a local id mapping from the string to
  186. * some component or from the component to some string. Otherwise
  187. * returns false.
  188. */
  189. public boolean setComponentLocalId(Component component, String localId) {
  190. return twoWayMap(localId, component, localIdToComponent,
  191. componentToLocalId);
  192. }
  193. /**
  194. * Returns the local id for a component.
  195. *
  196. * @since 7.5.0
  197. *
  198. * @param component
  199. * The component whose local id to get.
  200. * @return the local id of the component, or null if the component has no
  201. * local id assigned
  202. */
  203. public String getComponentLocalId(Component component) {
  204. return componentToLocalId.get(component);
  205. }
  206. /**
  207. * Creates a mapping between the given caption and the component. Returns
  208. * true if caption was already mapped to some component.
  209. *
  210. * Note that unlike mapGlobalId, if some component already has the given
  211. * caption, the caption is not cleared from the component. This allows
  212. * non-unique captions. However, only one of the components corresponding to
  213. * a given caption can be found using the map captionToComponent. Hence, any
  214. * captions that are used to identify an object should be unique.
  215. *
  216. * @param caption
  217. * The new caption of the component.
  218. * @param component
  219. * The component whose caption is to be set.
  220. * @return true, if there already was a caption mapping from the string to
  221. * some component.
  222. */
  223. private boolean mapCaption(String caption, Component component) {
  224. return captionToComponent.put(caption, component) != null;
  225. }
  226. /**
  227. * Creates a two-way mapping between key and value, i.e. adds key -> value
  228. * to keyToValue and value -> key to valueToKey. If key was mapped to a
  229. * value v different from the given value, the mapping from v to key is
  230. * removed. Similarly, if value was mapped to some key k different from key,
  231. * the mapping from k to value is removed.
  232. *
  233. * Returns true if there already was a mapping from key to some value v or
  234. * if there was a mapping from value to some key k. Otherwise returns false.
  235. *
  236. * @param key
  237. * The new key in keyToValue.
  238. * @param value
  239. * The new value in keyToValue.
  240. * @param keyToValue
  241. * A map from keys to values.
  242. * @param valueToKey
  243. * A map from values to keys.
  244. * @return whether there already was some mapping from key to a value or
  245. * from value to a key.
  246. */
  247. private <S, T> boolean twoWayMap(S key, T value, Map<S, T> keyToValue,
  248. Map<T, S> valueToKey) {
  249. T oldValue = keyToValue.put(key, value);
  250. if (oldValue != null && !oldValue.equals(value)) {
  251. valueToKey.remove(oldValue);
  252. }
  253. S oldKey = valueToKey.put(value, key);
  254. if (oldKey != null && !oldKey.equals(key)) {
  255. keyToValue.remove(oldKey);
  256. }
  257. return oldValue != null || oldKey != null;
  258. }
  259. /**
  260. * Creates a two-way mapping between a prefix and a package name.
  261. *
  262. * Note that modifying the mapping for {@value #VAADIN_UI_PACKAGE} may
  263. * invalidate the backwards compatibility mechanism supporting reading such
  264. * components with either {@value #LEGACY_PREFIX} or {@value #VAADIN_PREFIX}
  265. * as prefix.
  266. *
  267. * @param prefix
  268. * the prefix name without an ending dash (for instance, "vaadin"
  269. * is by default used for "com.vaadin.ui")
  270. * @param packageName
  271. * the name of the package corresponding to prefix
  272. *
  273. * @see #getPackagePrefixes()
  274. * @see #getPackagePrefix(String)
  275. * @see #getPackage(String)
  276. * @since 7.5.0
  277. */
  278. public void addPackagePrefix(String prefix, String packageName) {
  279. twoWayMap(prefix, packageName, prefixToPackage, packageToPrefix);
  280. }
  281. /**
  282. * Gets the prefix mapping for a given package, or <code>null</code> if
  283. * there is no mapping for the package.
  284. *
  285. * @see #addPackagePrefix(String, String)
  286. * @see #getPackagePrefixes()
  287. *
  288. * @since 7.5.0
  289. * @param packageName
  290. * the package name to get a prefix for
  291. * @return the prefix for the package, or <code>null</code> if no prefix is
  292. * registered
  293. */
  294. public String getPackagePrefix(String packageName) {
  295. if (VAADIN_UI_PACKAGE.equals(packageName)) {
  296. return isLegacyPrefixEnabled() ? LEGACY_PREFIX : VAADIN_PREFIX;
  297. } else {
  298. return packageToPrefix.get(packageName);
  299. }
  300. }
  301. /**
  302. * Gets all registered package prefixes.
  303. *
  304. *
  305. * @since 7.5.0
  306. * @see #getPackage(String)
  307. * @return a collection of package prefixes
  308. */
  309. public Collection<String> getPackagePrefixes() {
  310. return Collections.unmodifiableCollection(prefixToPackage.keySet());
  311. }
  312. /**
  313. * Gets the package corresponding to the give prefix, or <code>null</code>
  314. * no package has been registered for the prefix.
  315. *
  316. * @since 7.5.0
  317. * @see #addPackagePrefix(String, String)
  318. * @param prefix
  319. * the prefix to find a package for
  320. * @return the package prefix, or <code>null</code> if no package is
  321. * registered for the provided prefix
  322. */
  323. public String getPackage(String prefix) {
  324. return prefixToPackage.get(prefix);
  325. }
  326. /**
  327. * Returns the default instance for the given class. The instance must not
  328. * be modified by the caller.
  329. *
  330. * @param <T>
  331. * a component class
  332. * @param component
  333. * the component that determines the class
  334. * @return the default instance for the given class. The return value must
  335. * not be modified by the caller
  336. */
  337. @SuppressWarnings("unchecked")
  338. public <T> T getDefaultInstance(Component component) {
  339. // If the root is a @DesignRoot component, it can't use itself as a
  340. // reference or the written design will be empty
  341. // If the root component in some other way initializes itself in the
  342. // constructor
  343. if (getRootComponent() == component
  344. && component.getClass().isAnnotationPresent(DesignRoot.class)) {
  345. return (T) getDefaultInstance((Class<? extends Component>) component
  346. .getClass().getSuperclass());
  347. }
  348. return (T) getDefaultInstance(component.getClass());
  349. }
  350. private Component getDefaultInstance(
  351. Class<? extends Component> componentClass) {
  352. Component instance = instanceCache.get(componentClass);
  353. if (instance == null) {
  354. instance = instantiateClass(componentClass.getName());
  355. instanceCache.put(componentClass, instance);
  356. }
  357. return instance;
  358. }
  359. /**
  360. * Reads and stores the mappings from prefixes to package names from meta
  361. * tags located under <head> in the html document.
  362. *
  363. * @param doc
  364. * the document
  365. */
  366. protected void readPackageMappings(Document doc) {
  367. Element head = doc.head();
  368. if (head == null) {
  369. return;
  370. }
  371. for (Node child : head.childNodes()) {
  372. if (child instanceof Element) {
  373. Element childElement = (Element) child;
  374. if ("meta".equals(childElement.tagName())) {
  375. Attributes attributes = childElement.attributes();
  376. if (attributes.hasKey("name")
  377. && attributes.hasKey("content") && "package-mapping"
  378. .equals(attributes.get("name"))) {
  379. String contentString = attributes.get("content");
  380. String[] parts = contentString.split(":");
  381. if (parts.length != 2) {
  382. throw new DesignException("The meta tag '" + child
  383. + "' cannot be parsed.");
  384. }
  385. String prefixName = parts[0];
  386. String packageName = parts[1];
  387. addPackagePrefix(prefixName, packageName);
  388. }
  389. }
  390. }
  391. }
  392. }
  393. /**
  394. * Writes the package mappings (prefix -> package name) of this object to
  395. * the specified document.
  396. * <p>
  397. * The prefixes are stored as <meta> tags under <head> in the document.
  398. *
  399. * @param doc
  400. * the Jsoup document tree where the package mappings are written
  401. */
  402. public void writePackageMappings(Document doc) {
  403. Element head = doc.head();
  404. for (String prefix : getPackagePrefixes()) {
  405. // Only store the prefix-name mapping if it is not a default mapping
  406. // (such as "vaadin" -> "com.vaadin.ui")
  407. if (!VAADIN_PREFIX.equals(prefix) && !VAADIN7_PREFIX.equals(prefix)
  408. && !LEGACY_PREFIX.equals(prefix)) {
  409. Node newNode = doc.createElement("meta");
  410. newNode.attr("name", "package-mapping");
  411. String prefixToPackageName = prefix + ":" + getPackage(prefix);
  412. newNode.attr("content", prefixToPackageName);
  413. head.appendChild(newNode);
  414. }
  415. }
  416. }
  417. /**
  418. * Check whether the legacy prefix "v" or the default prefix "vaadin" should
  419. * be used when writing designs. The property or context parameter
  420. * {@link Constants#SERVLET_PARAMETER_LEGACY_DESIGN_PREFIX} can be used to
  421. * switch to the legacy prefix.
  422. *
  423. * @since 7.5.7
  424. * @return true to use the legacy prefix, false by default
  425. */
  426. protected boolean isLegacyPrefixEnabled() {
  427. if (legacyDesignPrefix != null) {
  428. return legacyDesignPrefix.booleanValue();
  429. }
  430. if (VaadinService.getCurrent() == null) {
  431. // This will happen at least in JUnit tests.
  432. return false;
  433. }
  434. DeploymentConfiguration configuration = VaadinService.getCurrent()
  435. .getDeploymentConfiguration();
  436. legacyDesignPrefix = configuration.getApplicationOrSystemProperty(
  437. Constants.SERVLET_PARAMETER_LEGACY_DESIGN_PREFIX, "false")
  438. .equals("true");
  439. return legacyDesignPrefix.booleanValue();
  440. }
  441. /**
  442. * Creates an html tree node corresponding to the given element. Also
  443. * initializes its attributes by calling writeDesign. As a result of the
  444. * writeDesign() call, this method creates the entire subtree rooted at the
  445. * returned Node.
  446. *
  447. * @param childComponent
  448. * The component with state that is written in to the node
  449. * @return An html tree node corresponding to the given component. The tag
  450. * name of the created node is derived from the class name of
  451. * childComponent.
  452. */
  453. public Element createElement(Component childComponent) {
  454. ComponentMapper componentMapper = Design.getComponentMapper();
  455. String tagName = componentMapper.componentToTag(childComponent, this);
  456. Element newElement = doc.createElement(tagName);
  457. childComponent.writeDesign(newElement, this);
  458. // Handle the local id. Global id and caption should have been taken
  459. // care of by writeDesign.
  460. String localId = componentToLocalId.get(childComponent);
  461. if (localId != null) {
  462. newElement.attr(LOCAL_ID_ATTRIBUTE, localId);
  463. }
  464. return newElement;
  465. }
  466. /**
  467. * Reads the given design node and creates the corresponding component tree.
  468. *
  469. * @param componentDesign
  470. * The design element containing the description of the component
  471. * to be created.
  472. * @return the root component of component tree
  473. */
  474. public Component readDesign(Element componentDesign) {
  475. // Create the component.
  476. Component component = instantiateComponent(componentDesign);
  477. readDesign(componentDesign, component);
  478. fireComponentCreatedEvent(componentToLocalId.get(component), component);
  479. return component;
  480. }
  481. /**
  482. *
  483. * Reads the given design node and populates the given component with the
  484. * corresponding component tree.
  485. * <p>
  486. * Additionally registers the component id, local id and caption of the
  487. * given component and all its children in the context
  488. *
  489. * @param componentDesign
  490. * The design element containing the description of the component
  491. * to be created
  492. * @param component
  493. * The component which corresponds to the design element
  494. */
  495. public void readDesign(Element componentDesign, Component component) {
  496. component.readDesign(componentDesign, this);
  497. // Get the ids and the caption of the component and store them in the
  498. // maps of this design context.
  499. org.jsoup.nodes.Attributes attributes = componentDesign.attributes();
  500. // global id: only update the mapping, the id has already been set for
  501. // the component
  502. String id = component.getId();
  503. if (id != null && !id.isEmpty()) {
  504. boolean mappingExists = mapId(id, component);
  505. if (mappingExists) {
  506. throw new DesignException(
  507. "The following global id is not unique: " + id);
  508. }
  509. }
  510. // local id: this is not a property of a component, so need to fetch it
  511. // from the attributes of componentDesign
  512. if (attributes.hasKey(LOCAL_ID_ATTRIBUTE)) {
  513. String localId = attributes.get(LOCAL_ID_ATTRIBUTE);
  514. boolean mappingExists = setComponentLocalId(component, localId);
  515. if (mappingExists) {
  516. throw new DesignException(
  517. "the following local id is not unique: " + localId);
  518. }
  519. }
  520. // caption: a property of a component, possibly not unique
  521. String caption = component.getCaption();
  522. if (caption != null) {
  523. mapCaption(caption, component);
  524. }
  525. }
  526. /**
  527. * Creates a Component corresponding to the given node. Does not set the
  528. * attributes for the created object.
  529. *
  530. * @param node
  531. * a node of an html tree
  532. * @return a Component corresponding to node, with no attributes set.
  533. */
  534. private Component instantiateComponent(Node node) {
  535. String tag = node.nodeName();
  536. ComponentMapper componentMapper = Design.getComponentMapper();
  537. Component component = componentMapper.tagToComponent(tag,
  538. Design.getComponentFactory(), this);
  539. assert tagEquals(tag, componentMapper.componentToTag(component, this));
  540. return component;
  541. }
  542. private boolean tagEquals(String tag1, String tag2) {
  543. return tag1.equals(tag2)
  544. || (hasVaadinPrefix(tag1) && hasVaadinPrefix(tag2));
  545. }
  546. private boolean hasVaadinPrefix(String tag) {
  547. return tag.startsWith(LEGACY_PREFIX + "-")
  548. || tag.startsWith(VAADIN_PREFIX + "-");
  549. }
  550. /**
  551. * Instantiates given class via ComponentFactory.
  552. *
  553. * @param qualifiedClassName
  554. * class name to instantiate
  555. * @return instance of a given class
  556. */
  557. private Component instantiateClass(String qualifiedClassName) {
  558. ComponentFactory factory = Design.getComponentFactory();
  559. Component component = factory.createComponent(qualifiedClassName, this);
  560. if (component == null) {
  561. throw new DesignException("Got unexpected null component from "
  562. + factory.getClass().getName() + " for class "
  563. + qualifiedClassName);
  564. }
  565. return component;
  566. }
  567. /**
  568. * Returns the root component of a created component hierarchy.
  569. *
  570. * @return the root component of the hierarchy
  571. */
  572. public Component getRootComponent() {
  573. return rootComponent;
  574. }
  575. /**
  576. * Sets the root component of a created component hierarchy.
  577. *
  578. * @param rootComponent
  579. * the root component of the hierarchy
  580. */
  581. public void setRootComponent(Component rootComponent) {
  582. this.rootComponent = rootComponent;
  583. }
  584. /**
  585. * Adds a component creation listener. The listener will be notified when
  586. * components are created while parsing a design template
  587. *
  588. * @param listener
  589. * the component creation listener to be added
  590. * @return a registration object for removing the listener
  591. */
  592. public Registration addComponentCreationListener(
  593. ComponentCreationListener listener) {
  594. listeners.add(listener);
  595. return () -> listeners.remove(listener);
  596. }
  597. /**
  598. * Removes a component creation listener.
  599. *
  600. * @param listener
  601. * the component creation listener to be removed
  602. * @deprecated Use a {@link Registration} object returned by
  603. * {@link #addComponentCreationListener(ComponentCreationListener)}
  604. * a listener
  605. */
  606. @Deprecated
  607. public void removeComponentCreationListener(
  608. ComponentCreationListener listener) {
  609. listeners.remove(listener);
  610. }
  611. /**
  612. * Fires component creation event
  613. *
  614. * @param localId
  615. * localId of the component
  616. * @param component
  617. * the component that was created
  618. */
  619. private void fireComponentCreatedEvent(String localId,
  620. Component component) {
  621. ComponentCreatedEvent event = new ComponentCreatedEvent(localId,
  622. component);
  623. for (ComponentCreationListener listener : listeners) {
  624. listener.componentCreated(event);
  625. }
  626. }
  627. /**
  628. * Interface to be implemented by component creation listeners.
  629. *
  630. * @author Vaadin Ltd
  631. */
  632. @FunctionalInterface
  633. public interface ComponentCreationListener extends Serializable {
  634. /**
  635. * Called when component has been created in the design context.
  636. *
  637. * @param event
  638. * the component creation event containing information on the
  639. * created component
  640. */
  641. public void componentCreated(ComponentCreatedEvent event);
  642. }
  643. /**
  644. * Component creation event that is fired when a component is created in
  645. * the. context
  646. *
  647. * @author Vaadin Ltd
  648. */
  649. public class ComponentCreatedEvent implements Serializable {
  650. private final String localId;
  651. private final Component component;
  652. private final DesignContext context;
  653. /**
  654. * Creates a new instance of ComponentCreatedEvent
  655. *
  656. * @param localId
  657. * the local id of the created component
  658. * @param component
  659. * the created component
  660. */
  661. private ComponentCreatedEvent(String localId, Component component) {
  662. this.localId = localId;
  663. this.component = component;
  664. context = DesignContext.this;
  665. }
  666. /**
  667. * Returns the local id of the created component or null if not exist.
  668. *
  669. * @return the localId
  670. */
  671. public String getLocalId() {
  672. return localId;
  673. }
  674. /**
  675. * Returns the created component.
  676. *
  677. * @return the component
  678. */
  679. public Component getComponent() {
  680. return component;
  681. }
  682. /**
  683. * Returns the new component context.
  684. *
  685. * @return the context
  686. *
  687. * @since 8.5
  688. */
  689. public DesignContext getContext() {
  690. return context;
  691. }
  692. }
  693. /**
  694. * Helper method for component write implementors to determine whether their
  695. * children should be written out or not.
  696. *
  697. * @param c
  698. * The component being written
  699. * @param defaultC
  700. * The default instance for the component
  701. * @return whether the children of c should be written
  702. */
  703. public boolean shouldWriteChildren(Component c, Component defaultC) {
  704. if (c == getRootComponent()) {
  705. // The root component should always write its children - otherwise
  706. // the result is empty
  707. return true;
  708. }
  709. if (defaultC instanceof HasComponents
  710. && ((HasComponents) defaultC).iterator().hasNext()) {
  711. // Easy version which assumes that this is a custom component if the
  712. // constructor adds children
  713. return false;
  714. }
  715. return true;
  716. }
  717. /**
  718. * Determines whether the container data of a component should be written
  719. * out by delegating to a {@link ShouldWriteDataDelegate}. The default
  720. * delegate assumes that all component data is provided by a data provider
  721. * connected to a back end system and that the data should thus not be
  722. * written.
  723. *
  724. * @since 7.5.0
  725. * @see #setShouldWriteDataDelegate(ShouldWriteDataDelegate)
  726. * @param component
  727. * the component to check
  728. * @return <code>true</code> if container data should be written out for the
  729. * provided component; otherwise <code>false</code>.
  730. */
  731. public boolean shouldWriteData(Component component) {
  732. return getShouldWriteDataDelegate().shouldWriteData(component);
  733. }
  734. /**
  735. * Sets the delegate that determines whether the container data of a
  736. * component should be written out.
  737. *
  738. * @since 7.5.0
  739. * @see #shouldWriteChildren(Component, Component)
  740. * @see #getShouldWriteDataDelegate()
  741. * @param shouldWriteDataDelegate
  742. * the delegate to set, not <code>null</code>
  743. * @throws IllegalArgumentException
  744. * if the provided delegate is <code>null</code>
  745. */
  746. public void setShouldWriteDataDelegate(
  747. ShouldWriteDataDelegate shouldWriteDataDelegate) {
  748. if (shouldWriteDataDelegate == null) {
  749. throw new IllegalArgumentException("Delegate cannot be null");
  750. }
  751. this.shouldWriteDataDelegate = shouldWriteDataDelegate;
  752. }
  753. /**
  754. * Gets the delegate that determines whether the container data of a
  755. * component should be written out.
  756. *
  757. * @since 7.5.0
  758. * @see #setShouldWriteDataDelegate(ShouldWriteDataDelegate)
  759. * @see #shouldWriteChildren(Component, Component)
  760. * @return the shouldWriteDataDelegate the currently use delegate
  761. */
  762. public ShouldWriteDataDelegate getShouldWriteDataDelegate() {
  763. return shouldWriteDataDelegate;
  764. }
  765. /**
  766. * Gets the attributes that the component did not handle.
  767. *
  768. * @since 7.7
  769. * @param component
  770. * the component to get the attributes for
  771. * @return map of the attributes which were not recognized by the component
  772. */
  773. public Map<String, String> getCustomAttributes(Component component) {
  774. return customAttributes.get(component);
  775. }
  776. /**
  777. * Sets a custom attribute not handled by the component. These attributes
  778. * are directly written to the component tag.
  779. *
  780. * @since 7.7
  781. * @param component
  782. * the component to set the attribute for
  783. * @param attribute
  784. * the attribute to set
  785. * @param value
  786. * the value of the attribute
  787. */
  788. public void setCustomAttribute(Component component, String attribute,
  789. String value) {
  790. Map<String, String> map = customAttributes.get(component);
  791. if (map == null) {
  792. map = new HashMap<>();
  793. customAttributes.put(component, map);
  794. }
  795. map.put(attribute, value);
  796. }
  797. /**
  798. * Set whether default attribute values should be written by the
  799. * {@code DesignAttributeHandler#writeAttribute(String, Attributes, Object, Object, Class, DesignContext)}
  800. * method. Default is {@code false}.
  801. *
  802. * @since 8.0
  803. * @param value
  804. * {@code true} to write default values of attributes,
  805. * {@code false} to disable writing of default values
  806. */
  807. public void setShouldWriteDefaultValues(boolean value) {
  808. shouldWriteDefaultValues = value;
  809. }
  810. /**
  811. * Determines whether default attribute values should be written by the
  812. * {@code DesignAttributeHandler#writeAttribute(String, Attributes, Object, Object, Class, DesignContext)}
  813. * method. Default is {@code false}.
  814. *
  815. * @since 8.0
  816. * @return {@code true} if default values of attributes should be written,
  817. * otherwise {@code false}
  818. */
  819. public boolean shouldWriteDefaultValues() {
  820. return shouldWriteDefaultValues;
  821. }
  822. }