選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

Design.java 28KB

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