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.

Design.java 30KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. /*
  2. * Copyright 2000-2016 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.beans.IntrospectionException;
  18. import java.io.IOException;
  19. import java.io.InputStream;
  20. import java.io.OutputStream;
  21. import java.io.Serializable;
  22. import java.lang.annotation.Annotation;
  23. import java.util.Collection;
  24. import java.util.Locale;
  25. import java.util.logging.Level;
  26. import java.util.logging.Logger;
  27. import org.jsoup.Jsoup;
  28. import org.jsoup.nodes.Document;
  29. import org.jsoup.nodes.Document.OutputSettings.Syntax;
  30. import org.jsoup.nodes.DocumentType;
  31. import org.jsoup.nodes.Element;
  32. import org.jsoup.nodes.Node;
  33. import org.jsoup.parser.Parser;
  34. import org.jsoup.select.Elements;
  35. import com.vaadin.annotations.DesignRoot;
  36. import com.vaadin.shared.util.SharedUtil;
  37. import com.vaadin.ui.Component;
  38. import com.vaadin.ui.declarative.DesignContext.ComponentCreatedEvent;
  39. import com.vaadin.ui.declarative.DesignContext.ComponentCreationListener;
  40. /**
  41. * Design is used for reading a component hierarchy from an html string or input
  42. * stream and, conversely, for writing an html representation corresponding to a
  43. * given component hierarchy.
  44. *
  45. * <p>
  46. * In html form a valid nonempty component hierarchy contains a single root
  47. * element located under the &lt;body&gt; tag. A hierarchy of components is
  48. * achieved by nesting other elements under the root element. An empty component
  49. * hierarchy is represented as no elements under the &lt;body&gt; tag.
  50. *
  51. * <p>
  52. * For writing a component hierarchy the root element is specified as a
  53. * Component parameter or as a DesignContext object containing the root
  54. * Component. An empty hierarchy can be written by giving a null root Component.
  55. *
  56. * @since 7.4
  57. * @author Vaadin Ltd
  58. */
  59. public class Design implements Serializable {
  60. private static final String UTF8 = "UTF-8";
  61. /**
  62. * Callback for creating instances of a given component class when reading
  63. * designs. The default implementation, {@link DefaultComponentFactory} will
  64. * use <code>Class.forName(className).newInstance()</code>, which might not
  65. * be suitable e.g. in an OSGi environment or if the Component instances
  66. * should be created as managed CDI beans.
  67. * <p>
  68. * Use {@link Design#setComponentFactory(ComponentFactory)} to configure
  69. * Vaadin to use a custom component factory.
  70. *
  71. * @since 7.4.1
  72. */
  73. public interface ComponentFactory extends Serializable {
  74. /**
  75. * Creates a component based on the fully qualified name derived from
  76. * the tag name in the design.
  77. *
  78. * @param fullyQualifiedClassName
  79. * the fully qualified name of the component to create
  80. * @param context
  81. * the design context for which the component is created
  82. *
  83. * @return a newly created component
  84. */
  85. public Component createComponent(String fullyQualifiedClassName,
  86. DesignContext context);
  87. }
  88. /**
  89. * Delegate for handling the mapping between tag names and component
  90. * instances.
  91. * <p>
  92. * Use {@link Design#setComponentMapper(ComponentMapper)} to configure
  93. * Vaadin to use a custom component mapper.
  94. *
  95. * @since 7.5.0
  96. * @author Vaadin Ltd
  97. */
  98. public interface ComponentMapper extends Serializable {
  99. /**
  100. * Resolves and creates a component using the provided component factory
  101. * based on a tag name.
  102. * <p>
  103. * This method should be in sync with
  104. * {@link #componentToTag(Component, DesignContext)} so that the
  105. * resolved tag for a created component is the same as the tag for which
  106. * the component was created.
  107. *
  108. * @param tag
  109. * the tag name to create a component for
  110. * @param componentFactory
  111. * the component factory that actually creates a component
  112. * based on a fully qualified class name
  113. * @param context
  114. * the design context for which the component is created
  115. * @return a newly created component
  116. */
  117. public Component tagToComponent(String tag,
  118. ComponentFactory componentFactory, DesignContext context);
  119. /**
  120. * Resolves a tag name from a component.
  121. *
  122. * @param component
  123. * the component to get a tag name for
  124. * @param context
  125. * the design context for which the tag name is needed
  126. * @return the tag name corresponding to the component
  127. */
  128. public String componentToTag(Component component,
  129. DesignContext context);
  130. }
  131. /**
  132. * Default implementation of {@link ComponentFactory}, using
  133. * <code>Class.forName(className).newInstance()</code> for finding the
  134. * component class and creating a component instance.
  135. *
  136. * @since 7.4.1
  137. */
  138. public static class DefaultComponentFactory implements ComponentFactory {
  139. @Override
  140. public Component createComponent(String fullyQualifiedClassName,
  141. DesignContext context) {
  142. Class<? extends Component> componentClass;
  143. try {
  144. componentClass = resolveComponentClass(fullyQualifiedClassName,
  145. context);
  146. } catch (DesignException e) {
  147. // Try with an inner class.
  148. int lastDot = fullyQualifiedClassName.lastIndexOf('.');
  149. if (lastDot != -1) {
  150. String qualifiedInnerClassName = fullyQualifiedClassName
  151. .substring(0, lastDot) + "$"
  152. + fullyQualifiedClassName.substring(lastDot + 1);
  153. return createComponent(qualifiedInnerClassName, context);
  154. } else {
  155. throw e;
  156. }
  157. }
  158. assert Component.class.isAssignableFrom(
  159. componentClass) : "resolveComponentClass returned "
  160. + componentClass
  161. + " which is not a Vaadin Component class";
  162. try {
  163. return componentClass.newInstance();
  164. } catch (Exception e) {
  165. throw new DesignException(
  166. "Could not create component " + fullyQualifiedClassName,
  167. e);
  168. }
  169. }
  170. /**
  171. * Resolves a component class based on the fully qualified name of the
  172. * class.
  173. *
  174. * @param qualifiedClassName
  175. * the fully qualified name of the resolved class
  176. * @param context
  177. * the design context for which the class is resolved
  178. * @return a component class object representing the provided class name
  179. */
  180. protected Class<? extends Component> resolveComponentClass(
  181. String qualifiedClassName, DesignContext context) {
  182. try {
  183. Class<?> componentClass = Class.forName(qualifiedClassName);
  184. return componentClass.asSubclass(Component.class);
  185. } catch (ClassNotFoundException e) {
  186. throw new DesignException("Unable to load component for design",
  187. e);
  188. }
  189. }
  190. }
  191. /**
  192. * Default implementation of {@link ComponentMapper},
  193. *
  194. * @since 7.5.0
  195. */
  196. public static class DefaultComponentMapper implements ComponentMapper {
  197. @Override
  198. public Component tagToComponent(String tagName,
  199. ComponentFactory componentFactory, DesignContext context) {
  200. // Extract the package and class names.
  201. // Otherwise, get the full class name using the prefix to package
  202. // mapping. Example: "vaadin-vertical-layout" ->
  203. // "com.vaadin.ui.VerticalLayout"
  204. String[] parts = tagName.split("-", 2);
  205. if (parts.length < 2) {
  206. throw new DesignException("The tagname '" + tagName
  207. + "' is invalid: missing prefix.");
  208. }
  209. String prefixName = parts[0];
  210. String packageName = context.getPackage(prefixName);
  211. if (packageName == null) {
  212. throw new DesignException("Unknown tag: " + tagName);
  213. }
  214. String[] classNameParts = parts[1].split("-");
  215. String className = "";
  216. for (String classNamePart : classNameParts) {
  217. // Split will ignore trailing and multiple dashes but that
  218. // should be
  219. // ok
  220. // <vaadin-button--> will be resolved to <vaadin-button>
  221. // <vaadin--button> will be resolved to <vaadin-button>
  222. className += SharedUtil.capitalize(classNamePart);
  223. }
  224. String qualifiedClassName = packageName + "." + className;
  225. Component component = componentFactory
  226. .createComponent(qualifiedClassName, context);
  227. if (component == null) {
  228. throw new DesignException("Got unexpected null component from "
  229. + componentFactory.getClass().getName() + " for class "
  230. + qualifiedClassName);
  231. }
  232. return component;
  233. }
  234. @Override
  235. public String componentToTag(Component component,
  236. DesignContext context) {
  237. Class<?> componentClass = component.getClass();
  238. String packageName = getPackageName(componentClass);
  239. String prefix = context.getPackagePrefix(packageName);
  240. if (prefix == null) {
  241. prefix = packageName.replace('.', '_')
  242. .toLowerCase(Locale.ENGLISH);
  243. context.addPackagePrefix(prefix, packageName);
  244. }
  245. prefix = prefix + "-";
  246. String className = classNameToElementName(
  247. componentClass.getSimpleName());
  248. String tagName = prefix + className;
  249. return tagName;
  250. }
  251. private String getPackageName(Class<?> componentClass) {
  252. if (componentClass.isMemberClass()) {
  253. Class<?> enclosingClass = componentClass.getEnclosingClass();
  254. return getPackageName(enclosingClass) + "."
  255. + enclosingClass.getSimpleName();
  256. } else {
  257. return componentClass.getPackage().getName();
  258. }
  259. }
  260. /**
  261. * Creates the name of the html tag corresponding to the given class
  262. * name. The name is derived by converting each uppercase letter to
  263. * lowercase and inserting a dash before the letter. No dash is inserted
  264. * before the first letter of the class name.
  265. *
  266. * @param className
  267. * the name of the class without a package name
  268. * @return the html tag name corresponding to className
  269. */
  270. private String classNameToElementName(String className) {
  271. StringBuilder result = new StringBuilder();
  272. for (int i = 0; i < className.length(); i++) {
  273. Character c = className.charAt(i);
  274. if (Character.isUpperCase(c)) {
  275. if (i > 0) {
  276. result.append("-");
  277. }
  278. result.append(Character.toLowerCase(c));
  279. } else {
  280. result.append(c);
  281. }
  282. }
  283. return result.toString();
  284. }
  285. }
  286. private static volatile ComponentFactory componentFactory = new DefaultComponentFactory();
  287. private static volatile ComponentMapper componentMapper = new DefaultComponentMapper();
  288. /**
  289. * Sets the component factory that is used for creating component instances
  290. * based on fully qualified class names derived from a design file.
  291. * <p>
  292. * Please note that this setting is global, so care should be taken to avoid
  293. * conflicting changes.
  294. *
  295. * @param componentFactory
  296. * the component factory to set; not <code>null</code>
  297. *
  298. * @since 7.4.1
  299. */
  300. public static void setComponentFactory(ComponentFactory componentFactory) {
  301. if (componentFactory == null) {
  302. throw new IllegalArgumentException(
  303. "Cannot set null component factory");
  304. }
  305. Design.componentFactory = componentFactory;
  306. }
  307. /**
  308. * Gets the currently used component factory.
  309. *
  310. * @see #setComponentFactory(ComponentFactory)
  311. *
  312. * @return the component factory
  313. *
  314. * @since 7.4.1
  315. */
  316. public static ComponentFactory getComponentFactory() {
  317. return componentFactory;
  318. }
  319. /**
  320. * Sets the component mapper that is used for resolving between tag names
  321. * and component instances.
  322. * <p>
  323. * Please note that this setting is global, so care should be taken to avoid
  324. * conflicting changes.
  325. *
  326. * @param componentMapper
  327. * the component mapper to set; not <code>null</code>
  328. *
  329. * @since 7.5.0
  330. */
  331. public static void setComponentMapper(ComponentMapper componentMapper) {
  332. if (componentMapper == null) {
  333. throw new IllegalArgumentException(
  334. "Cannot set null component mapper");
  335. }
  336. Design.componentMapper = componentMapper;
  337. }
  338. /**
  339. * Gets the currently used component mapper.
  340. *
  341. * @see #setComponentMapper(ComponentMapper)
  342. *
  343. * @return the component mapper
  344. *
  345. * @since 7.5.0
  346. */
  347. public static ComponentMapper getComponentMapper() {
  348. return componentMapper;
  349. }
  350. /**
  351. * Parses the given input stream into a jsoup document
  352. *
  353. * @param html
  354. * the stream containing the design
  355. * @return the parsed jsoup document
  356. * @throws IOException
  357. */
  358. private static Document parse(InputStream html) {
  359. try {
  360. Document doc = Jsoup.parse(html, UTF8, "", Parser.htmlParser());
  361. return doc;
  362. } catch (IOException e) {
  363. throw new DesignException("The html document cannot be parsed.");
  364. }
  365. }
  366. /**
  367. * Constructs a component hierarchy from the design specified as an html
  368. * tree.
  369. *
  370. * <p>
  371. * If a component root is given, the component instances created during
  372. * reading the design are assigned to its member fields based on their id,
  373. * local id, and caption
  374. *
  375. * @param doc
  376. * the html tree
  377. * @param componentRoot
  378. * optional component root instance. The type must match the type
  379. * of the root element in the design. Any member fields whose
  380. * type is assignable from {@link Component} are bound to fields
  381. * in the design based on id/local id/caption
  382. */
  383. private static DesignContext designToComponentTree(Document doc,
  384. Component componentRoot) {
  385. if (componentRoot == null) {
  386. return designToComponentTree(doc, null, null);
  387. } else {
  388. return designToComponentTree(doc, componentRoot,
  389. componentRoot.getClass());
  390. }
  391. }
  392. /**
  393. * Constructs a component hierarchy from the design specified as an html
  394. * tree.
  395. *
  396. * <p>
  397. * If a component root is given, the component instances created during
  398. * reading the design are assigned to its member fields based on their id,
  399. * local id, and caption
  400. *
  401. * @param doc
  402. * the html tree
  403. * @param componentRoot
  404. * optional component root instance. The type must match the type
  405. * of the root element in the design.
  406. * @param classWithFields
  407. * a class (componentRoot class or a super class) with some
  408. * member fields. The member fields whose type is assignable from
  409. * {@link Component} are bound to fields in the design based on
  410. * id/local id/caption
  411. */
  412. private static DesignContext designToComponentTree(Document doc,
  413. Component componentRoot, Class<?> classWithFields) {
  414. DesignContext designContext = new DesignContext(doc);
  415. designContext.readPackageMappings(doc);
  416. // No special handling for a document without a body element - should be
  417. // taken care of by jsoup.
  418. Element root = doc.body();
  419. Elements children = root.children();
  420. if (children.size() > 1) {
  421. throw new DesignException(
  422. "The first level of a component hierarchy should contain at most one root component, but found "
  423. + children.size() + ".");
  424. }
  425. Element element = children.size() == 0 ? null : children.first();
  426. if (componentRoot != null) {
  427. if (element == null) {
  428. throw new DesignException(
  429. "The root element cannot be null when the specified root Component is"
  430. + " not null.");
  431. }
  432. // user has specified root instance that may have member fields that
  433. // should be bound
  434. final FieldBinder binder;
  435. try {
  436. binder = new FieldBinder(componentRoot, classWithFields);
  437. } catch (IntrospectionException e) {
  438. throw new DesignException(
  439. "Could not bind fields of the root component", e);
  440. }
  441. // create listener for component creations that binds the created
  442. // components to the componentRoot instance fields
  443. ComponentCreationListener creationListener = new ComponentCreationListener() {
  444. @Override
  445. public void componentCreated(ComponentCreatedEvent event) {
  446. binder.bindField(event.getComponent(), event.getLocalId());
  447. }
  448. };
  449. designContext.addComponentCreationListener(creationListener);
  450. // create subtree
  451. designContext.readDesign(element, componentRoot);
  452. // make sure that all the member fields are bound
  453. Collection<String> unboundFields = binder.getUnboundFields();
  454. if (!unboundFields.isEmpty()) {
  455. throw new DesignException(
  456. "Found unbound fields from component root "
  457. + unboundFields);
  458. }
  459. // no need to listen anymore
  460. designContext.removeComponentCreationListener(creationListener);
  461. } else {
  462. // createChild creates the entire component hierarchy
  463. componentRoot = element == null ? null
  464. : designContext.readDesign(element);
  465. }
  466. designContext.setRootComponent(componentRoot);
  467. return designContext;
  468. }
  469. /**
  470. * Generates an html tree representation of the component hierarchy having
  471. * the root designContext.getRootComponent(). The hierarchy is stored under
  472. * &lt;body&gt; in the tree. The generated tree represents a valid html
  473. * document.
  474. *
  475. *
  476. * @param designContext
  477. * a DesignContext object specifying the root component
  478. * (designContext.getRootComponent()) of the hierarchy
  479. * @return an html tree representation of the component hierarchy
  480. */
  481. private static Document createHtml(DesignContext designContext) {
  482. // Create the html tree skeleton.
  483. Document doc = new Document("");
  484. DocumentType docType = new DocumentType("html", "", "", "");
  485. doc.appendChild(docType);
  486. Element html = doc.createElement("html");
  487. doc.appendChild(html);
  488. html.appendChild(doc.createElement("head"));
  489. Element body = doc.createElement("body");
  490. html.appendChild(body);
  491. // Append the design under <body> in the html tree. createNode
  492. // creates the entire component hierarchy rooted at the
  493. // given root node.
  494. Component root = designContext.getRootComponent();
  495. if (root != null) {
  496. Node rootNode = designContext.createElement(root);
  497. body.appendChild(rootNode);
  498. }
  499. designContext.writePackageMappings(doc);
  500. return doc;
  501. }
  502. /**
  503. * Loads a design for the given root component.
  504. * <p>
  505. * This methods assumes that the component class (or a super class) has been
  506. * marked with an {@link DesignRoot} annotation and will either use the
  507. * value from the annotation to locate the design file, or will fall back to
  508. * using a design with the same same as the annotated class file (with an
  509. * .html extension)
  510. * <p>
  511. * Any {@link Component} type fields in the root component which are not
  512. * assigned (i.e. are null) are mapped to corresponding components in the
  513. * design. Matching is done based on field name in the component class and
  514. * id/local id/caption in the design file.
  515. * <p>
  516. * The type of the root component must match the root element in the design
  517. *
  518. * @param rootComponent
  519. * The root component of the layout
  520. * @return The design context used in the load operation
  521. * @throws DesignException
  522. * If the design could not be loaded
  523. */
  524. public static DesignContext read(Component rootComponent)
  525. throws DesignException {
  526. // Try to find an @DesignRoot annotation on the class or any parent
  527. // class
  528. Class<? extends Component> annotatedClass = findClassWithAnnotation(
  529. rootComponent.getClass(), DesignRoot.class);
  530. if (annotatedClass == null) {
  531. throw new IllegalArgumentException("The class "
  532. + rootComponent.getClass().getName()
  533. + " or any of its superclasses do not have an @DesignRoot annotation");
  534. }
  535. DesignRoot designAnnotation = annotatedClass
  536. .getAnnotation(DesignRoot.class);
  537. String filename = designAnnotation.value();
  538. if (filename.equals("")) {
  539. // No value, assume the html file is named as the class
  540. filename = annotatedClass.getSimpleName() + ".html";
  541. }
  542. InputStream stream = annotatedClass.getResourceAsStream(filename);
  543. if (stream == null) {
  544. throw new DesignException("Unable to find design file " + filename
  545. + " in " + annotatedClass.getPackage().getName());
  546. }
  547. try {
  548. Document doc = parse(stream);
  549. DesignContext context = designToComponentTree(doc, rootComponent,
  550. annotatedClass);
  551. return context;
  552. } finally {
  553. try {
  554. stream.close();
  555. } catch (IOException e) {
  556. getLogger().log(Level.FINE, "Error closing design stream", e);
  557. }
  558. }
  559. }
  560. private static Logger getLogger() {
  561. return Logger.getLogger(Design.class.getName());
  562. }
  563. /**
  564. * Find the first class with the given annotation, starting the search from
  565. * the given class and moving upwards in the class hierarchy.
  566. *
  567. * @param componentClass
  568. * the class to check
  569. * @param annotationClass
  570. * the annotation to look for
  571. * @return the first class with the given annotation or null if no class
  572. * with the annotation was found
  573. */
  574. private static Class<? extends Component> findClassWithAnnotation(
  575. Class<? extends Component> componentClass,
  576. Class<? extends Annotation> annotationClass) {
  577. if (componentClass == null) {
  578. return null;
  579. }
  580. if (componentClass.isAnnotationPresent(annotationClass)) {
  581. return componentClass;
  582. }
  583. Class<?> superClass = componentClass.getSuperclass();
  584. if (!Component.class.isAssignableFrom(superClass)) {
  585. return null;
  586. }
  587. return findClassWithAnnotation(superClass.asSubclass(Component.class),
  588. annotationClass);
  589. }
  590. /**
  591. * Loads a design from the given file name using the given root component.
  592. * <p>
  593. * Any {@link Component} type fields in the root component which are not
  594. * assigned (i.e. are null) are mapped to corresponding components in the
  595. * design. Matching is done based on field name in the component class and
  596. * id/local id/caption in the design file.
  597. * <p>
  598. * The type of the root component must match the root element in the design.
  599. *
  600. * @param filename
  601. * The file name to load. Loaded from the same package as the
  602. * root component
  603. * @param rootComponent
  604. * The root component of the layout
  605. * @return The design context used in the load operation
  606. * @throws DesignException
  607. * If the design could not be loaded
  608. */
  609. public static DesignContext read(String filename, Component rootComponent)
  610. throws DesignException {
  611. InputStream stream = rootComponent.getClass()
  612. .getResourceAsStream(filename);
  613. if (stream == null) {
  614. throw new DesignException(
  615. "File " + filename + " was not found in the package "
  616. + rootComponent.getClass().getPackage().getName());
  617. }
  618. try {
  619. return read(stream, rootComponent);
  620. } finally {
  621. try {
  622. stream.close();
  623. } catch (IOException e) {
  624. getLogger().log(Level.FINE, "Error closing design stream", e);
  625. }
  626. }
  627. }
  628. /**
  629. * Loads a design from the given stream using the given root component. If
  630. * rootComponent is null, the type of the root node is read from the design.
  631. * <p>
  632. * Any {@link Component} type fields in the root component which are not
  633. * assigned (i.e. are null) are mapped to corresponding components in the
  634. * design. Matching is done based on field name in the component class and
  635. * id/local id/caption in the design file.
  636. * <p>
  637. * If rootComponent is not null, its type must match the type of the root
  638. * element in the design
  639. *
  640. * @param stream
  641. * The stream to read the design from
  642. * @param rootComponent
  643. * The root component of the layout
  644. * @return The design context used in the load operation
  645. * @throws DesignException
  646. * If the design could not be loaded
  647. */
  648. public static DesignContext read(InputStream stream,
  649. Component rootComponent) {
  650. if (stream == null) {
  651. throw new DesignException("Stream cannot be null");
  652. }
  653. Document doc = parse(stream);
  654. DesignContext context = designToComponentTree(doc, rootComponent);
  655. return context;
  656. }
  657. /**
  658. * Loads a design from the given input stream
  659. *
  660. * @param design
  661. * The stream to read the design from
  662. * @return The root component of the design
  663. */
  664. public static Component read(InputStream design) {
  665. DesignContext context = read(design, null);
  666. return context.getRootComponent();
  667. }
  668. /**
  669. * Writes the given component tree in design format to the given output
  670. * stream.
  671. *
  672. * @param component
  673. * the root component of the component tree, null can be used for
  674. * generating an empty design
  675. * @param outputStream
  676. * the output stream to write the design to. The design is always
  677. * written as UTF-8
  678. * @throws IOException
  679. * if writing fails
  680. */
  681. public static void write(Component component, OutputStream outputStream)
  682. throws IOException {
  683. DesignContext dc = new DesignContext();
  684. dc.setRootComponent(component);
  685. write(dc, outputStream);
  686. }
  687. /**
  688. * Writes the component, given in the design context, in design format to
  689. * the given output stream. The design context is used for writing local ids
  690. * and other information not available in the component tree.
  691. *
  692. * @param designContext
  693. * The DesignContext object specifying the component hierarchy
  694. * and the local id values of the objects. If
  695. * designContext.getRootComponent() is null, an empty design will
  696. * be generated.
  697. * @param outputStream
  698. * the output stream to write the design to. The design is always
  699. * written as UTF-8
  700. * @throws IOException
  701. * if writing fails
  702. */
  703. public static void write(DesignContext designContext,
  704. OutputStream outputStream) throws IOException {
  705. Document doc = createHtml(designContext);
  706. write(doc, outputStream);
  707. }
  708. /**
  709. * Writes the given jsoup document to the output stream (in UTF-8)
  710. *
  711. * @param doc
  712. * the document to write
  713. * @param outputStream
  714. * the stream to write to
  715. * @throws IOException
  716. * if writing fails
  717. */
  718. private static void write(Document doc, OutputStream outputStream)
  719. throws IOException {
  720. doc.outputSettings().indentAmount(4);
  721. doc.outputSettings().syntax(Syntax.html);
  722. doc.outputSettings().prettyPrint(true);
  723. outputStream.write(doc.html().getBytes(UTF8));
  724. }
  725. }