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.

ComponentSizeValidator.java 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.server;
  5. import java.io.PrintStream;
  6. import java.io.PrintWriter;
  7. import java.io.Serializable;
  8. import java.util.HashMap;
  9. import java.util.Iterator;
  10. import java.util.LinkedList;
  11. import java.util.List;
  12. import java.util.Map;
  13. import java.util.Stack;
  14. import java.util.Vector;
  15. import java.util.logging.Level;
  16. import java.util.logging.Logger;
  17. import com.vaadin.terminal.Sizeable.Unit;
  18. import com.vaadin.ui.AbstractOrderedLayout;
  19. import com.vaadin.ui.AbstractSplitPanel;
  20. import com.vaadin.ui.Component;
  21. import com.vaadin.ui.ComponentContainer;
  22. import com.vaadin.ui.CustomComponent;
  23. import com.vaadin.ui.Form;
  24. import com.vaadin.ui.GridLayout;
  25. import com.vaadin.ui.GridLayout.Area;
  26. import com.vaadin.ui.Layout;
  27. import com.vaadin.ui.Panel;
  28. import com.vaadin.ui.TabSheet;
  29. import com.vaadin.ui.VerticalLayout;
  30. import com.vaadin.ui.Window;
  31. @SuppressWarnings({ "serial", "deprecation" })
  32. public class ComponentSizeValidator implements Serializable {
  33. private final static Logger logger = Logger
  34. .getLogger(ComponentSizeValidator.class.getName());
  35. private final static int LAYERS_SHOWN = 4;
  36. /**
  37. * Recursively checks given component and its subtree for invalid layout
  38. * setups. Prints errors to std err stream.
  39. *
  40. * @param component
  41. * component to check
  42. * @return set of first level errors found
  43. */
  44. public static List<InvalidLayout> validateComponentRelativeSizes(
  45. Component component, List<InvalidLayout> errors,
  46. InvalidLayout parent) {
  47. boolean invalidHeight = !checkHeights(component);
  48. boolean invalidWidth = !checkWidths(component);
  49. if (invalidHeight || invalidWidth) {
  50. InvalidLayout error = new InvalidLayout(component, invalidHeight,
  51. invalidWidth);
  52. if (parent != null) {
  53. parent.addError(error);
  54. } else {
  55. if (errors == null) {
  56. errors = new LinkedList<InvalidLayout>();
  57. }
  58. errors.add(error);
  59. }
  60. parent = error;
  61. }
  62. if (component instanceof Panel) {
  63. Panel panel = (Panel) component;
  64. errors = validateComponentRelativeSizes(panel.getContent(), errors,
  65. parent);
  66. } else if (component instanceof ComponentContainer) {
  67. ComponentContainer lo = (ComponentContainer) component;
  68. Iterator<Component> it = lo.getComponentIterator();
  69. while (it.hasNext()) {
  70. errors = validateComponentRelativeSizes(it.next(), errors,
  71. parent);
  72. }
  73. } else if (component instanceof Form) {
  74. Form form = (Form) component;
  75. if (form.getLayout() != null) {
  76. errors = validateComponentRelativeSizes(form.getLayout(),
  77. errors, parent);
  78. }
  79. if (form.getFooter() != null) {
  80. errors = validateComponentRelativeSizes(form.getFooter(),
  81. errors, parent);
  82. }
  83. }
  84. return errors;
  85. }
  86. private static void printServerError(String msg,
  87. Stack<ComponentInfo> attributes, boolean widthError,
  88. PrintStream errorStream) {
  89. StringBuffer err = new StringBuffer();
  90. err.append("Vaadin DEBUG\n");
  91. StringBuilder indent = new StringBuilder("");
  92. ComponentInfo ci;
  93. if (attributes != null) {
  94. while (attributes.size() > LAYERS_SHOWN) {
  95. attributes.pop();
  96. }
  97. while (!attributes.empty()) {
  98. ci = attributes.pop();
  99. showComponent(ci.component, ci.info, err, indent, widthError);
  100. }
  101. }
  102. err.append("Layout problem detected: ");
  103. err.append(msg);
  104. err.append("\n");
  105. err.append("Relative sizes were replaced by undefined sizes, components may not render as expected.\n");
  106. errorStream.println(err);
  107. }
  108. public static boolean checkHeights(Component component) {
  109. try {
  110. if (!hasRelativeHeight(component)) {
  111. return true;
  112. }
  113. if (component instanceof Window) {
  114. return true;
  115. }
  116. if (component.getParent() == null) {
  117. return true;
  118. }
  119. return parentCanDefineHeight(component);
  120. } catch (Exception e) {
  121. logger.log(Level.FINER,
  122. "An exception occurred while validating sizes.", e);
  123. return true;
  124. }
  125. }
  126. public static boolean checkWidths(Component component) {
  127. try {
  128. if (!hasRelativeWidth(component)) {
  129. return true;
  130. }
  131. if (component instanceof Window) {
  132. return true;
  133. }
  134. if (component.getParent() == null) {
  135. return true;
  136. }
  137. return parentCanDefineWidth(component);
  138. } catch (Exception e) {
  139. logger.log(Level.FINER,
  140. "An exception occurred while validating sizes.", e);
  141. return true;
  142. }
  143. }
  144. public static class InvalidLayout implements Serializable {
  145. private final Component component;
  146. private final boolean invalidHeight;
  147. private final boolean invalidWidth;
  148. private final Vector<InvalidLayout> subErrors = new Vector<InvalidLayout>();
  149. public InvalidLayout(Component component, boolean height, boolean width) {
  150. this.component = component;
  151. invalidHeight = height;
  152. invalidWidth = width;
  153. }
  154. public void addError(InvalidLayout error) {
  155. subErrors.add(error);
  156. }
  157. public void reportErrors(PrintWriter clientJSON,
  158. AbstractCommunicationManager communicationManager,
  159. PrintStream serverErrorStream) {
  160. clientJSON.write("{");
  161. Component parent = component.getParent();
  162. String paintableId = component.getConnectorId();
  163. clientJSON.print("id:\"" + paintableId + "\"");
  164. if (invalidHeight) {
  165. Stack<ComponentInfo> attributes = null;
  166. String msg = "";
  167. // set proper error messages
  168. if (parent instanceof AbstractOrderedLayout) {
  169. AbstractOrderedLayout ol = (AbstractOrderedLayout) parent;
  170. boolean vertical = false;
  171. if (ol instanceof VerticalLayout) {
  172. vertical = true;
  173. }
  174. if (vertical) {
  175. msg = "Component with relative height inside a VerticalLayout with no height defined.";
  176. attributes = getHeightAttributes(component);
  177. } else {
  178. msg = "At least one of a HorizontalLayout's components must have non relative height if the height of the layout is not defined";
  179. attributes = getHeightAttributes(component);
  180. }
  181. } else if (parent instanceof GridLayout) {
  182. msg = "At least one of the GridLayout's components in each row should have non relative height if the height of the layout is not defined.";
  183. attributes = getHeightAttributes(component);
  184. } else {
  185. // default error for non sized parent issue
  186. msg = "A component with relative height needs a parent with defined height.";
  187. attributes = getHeightAttributes(component);
  188. }
  189. printServerError(msg, attributes, false, serverErrorStream);
  190. clientJSON.print(",\"heightMsg\":\"" + msg + "\"");
  191. }
  192. if (invalidWidth) {
  193. Stack<ComponentInfo> attributes = null;
  194. String msg = "";
  195. if (parent instanceof AbstractOrderedLayout) {
  196. AbstractOrderedLayout ol = (AbstractOrderedLayout) parent;
  197. boolean horizontal = true;
  198. if (ol instanceof VerticalLayout) {
  199. horizontal = false;
  200. }
  201. if (horizontal) {
  202. msg = "Component with relative width inside a HorizontalLayout with no width defined";
  203. attributes = getWidthAttributes(component);
  204. } else {
  205. msg = "At least one of a VerticalLayout's components must have non relative width if the width of the layout is not defined";
  206. attributes = getWidthAttributes(component);
  207. }
  208. } else if (parent instanceof GridLayout) {
  209. msg = "At least one of the GridLayout's components in each column should have non relative width if the width of the layout is not defined.";
  210. attributes = getWidthAttributes(component);
  211. } else {
  212. // default error for non sized parent issue
  213. msg = "A component with relative width needs a parent with defined width.";
  214. attributes = getWidthAttributes(component);
  215. }
  216. clientJSON.print(",\"widthMsg\":\"" + msg + "\"");
  217. printServerError(msg, attributes, true, serverErrorStream);
  218. }
  219. if (subErrors.size() > 0) {
  220. serverErrorStream.println("Sub errors >>");
  221. clientJSON.write(", \"subErrors\" : [");
  222. boolean first = true;
  223. for (InvalidLayout subError : subErrors) {
  224. if (!first) {
  225. clientJSON.print(",");
  226. } else {
  227. first = false;
  228. }
  229. subError.reportErrors(clientJSON, communicationManager,
  230. serverErrorStream);
  231. }
  232. clientJSON.write("]");
  233. serverErrorStream.println("<< Sub erros");
  234. }
  235. clientJSON.write("}");
  236. }
  237. }
  238. private static class ComponentInfo implements Serializable {
  239. Component component;
  240. String info;
  241. public ComponentInfo(Component component, String info) {
  242. this.component = component;
  243. this.info = info;
  244. }
  245. }
  246. private static Stack<ComponentInfo> getHeightAttributes(Component component) {
  247. Stack<ComponentInfo> attributes = new Stack<ComponentInfo>();
  248. attributes
  249. .add(new ComponentInfo(component, getHeightString(component)));
  250. Component parent = component.getParent();
  251. attributes.add(new ComponentInfo(parent, getHeightString(parent)));
  252. while ((parent = parent.getParent()) != null) {
  253. attributes.add(new ComponentInfo(parent, getHeightString(parent)));
  254. }
  255. return attributes;
  256. }
  257. private static Stack<ComponentInfo> getWidthAttributes(Component component) {
  258. Stack<ComponentInfo> attributes = new Stack<ComponentInfo>();
  259. attributes.add(new ComponentInfo(component, getWidthString(component)));
  260. Component parent = component.getParent();
  261. attributes.add(new ComponentInfo(parent, getWidthString(parent)));
  262. while ((parent = parent.getParent()) != null) {
  263. attributes.add(new ComponentInfo(parent, getWidthString(parent)));
  264. }
  265. return attributes;
  266. }
  267. private static String getWidthString(Component component) {
  268. String width = "width: ";
  269. if (hasRelativeWidth(component)) {
  270. width += "RELATIVE, " + component.getWidth() + " %";
  271. } else if (component instanceof Window && component.getParent() == null) {
  272. width += "MAIN WINDOW";
  273. } else if (component.getWidth() >= 0) {
  274. width += "ABSOLUTE, " + component.getWidth() + " "
  275. + component.getWidthUnits().getSymbol();
  276. } else {
  277. width += "UNDEFINED";
  278. }
  279. return width;
  280. }
  281. private static String getHeightString(Component component) {
  282. String height = "height: ";
  283. if (hasRelativeHeight(component)) {
  284. height += "RELATIVE, " + component.getHeight() + " %";
  285. } else if (component instanceof Window && component.getParent() == null) {
  286. height += "MAIN WINDOW";
  287. } else if (component.getHeight() > 0) {
  288. height += "ABSOLUTE, " + component.getHeight() + " "
  289. + component.getHeightUnits().getSymbol();
  290. } else {
  291. height += "UNDEFINED";
  292. }
  293. return height;
  294. }
  295. private static void showComponent(Component component, String attribute,
  296. StringBuffer err, StringBuilder indent, boolean widthError) {
  297. FileLocation createLoc = creationLocations.get(component);
  298. FileLocation sizeLoc;
  299. if (widthError) {
  300. sizeLoc = widthLocations.get(component);
  301. } else {
  302. sizeLoc = heightLocations.get(component);
  303. }
  304. err.append(indent);
  305. indent.append(" ");
  306. err.append("- ");
  307. err.append(component.getClass().getSimpleName());
  308. err.append("/").append(Integer.toHexString(component.hashCode()));
  309. if (component.getCaption() != null) {
  310. err.append(" \"");
  311. err.append(component.getCaption());
  312. err.append("\"");
  313. }
  314. if (component.getDebugId() != null) {
  315. err.append(" debugId: ");
  316. err.append(component.getDebugId());
  317. }
  318. if (createLoc != null) {
  319. err.append(", created at (" + createLoc.file + ":"
  320. + createLoc.lineNumber + ")");
  321. }
  322. if (attribute != null) {
  323. err.append(" (");
  324. err.append(attribute);
  325. if (sizeLoc != null) {
  326. err.append(", set at (" + sizeLoc.file + ":"
  327. + sizeLoc.lineNumber + ")");
  328. }
  329. err.append(")");
  330. }
  331. err.append("\n");
  332. }
  333. private static boolean hasNonRelativeHeightComponent(
  334. AbstractOrderedLayout ol) {
  335. Iterator<Component> it = ol.getComponentIterator();
  336. while (it.hasNext()) {
  337. if (!hasRelativeHeight(it.next())) {
  338. return true;
  339. }
  340. }
  341. return false;
  342. }
  343. public static boolean parentCanDefineHeight(Component component) {
  344. Component parent = component.getParent();
  345. if (parent == null) {
  346. // main window, valid situation
  347. return true;
  348. }
  349. if (parent.getHeight() < 0) {
  350. // Undefined height
  351. if (parent instanceof Window) {
  352. // Sub window with undefined size has a min-height
  353. return true;
  354. }
  355. if (parent instanceof AbstractOrderedLayout) {
  356. boolean horizontal = true;
  357. if (parent instanceof VerticalLayout) {
  358. horizontal = false;
  359. }
  360. if (horizontal
  361. && hasNonRelativeHeightComponent((AbstractOrderedLayout) parent)) {
  362. return true;
  363. } else {
  364. return false;
  365. }
  366. } else if (parent instanceof GridLayout) {
  367. GridLayout gl = (GridLayout) parent;
  368. Area componentArea = gl.getComponentArea(component);
  369. boolean rowHasHeight = false;
  370. for (int row = componentArea.getRow1(); !rowHasHeight
  371. && row <= componentArea.getRow2(); row++) {
  372. for (int column = 0; !rowHasHeight
  373. && column < gl.getColumns(); column++) {
  374. Component c = gl.getComponent(column, row);
  375. if (c != null) {
  376. rowHasHeight = !hasRelativeHeight(c);
  377. }
  378. }
  379. }
  380. if (!rowHasHeight) {
  381. return false;
  382. } else {
  383. // Other components define row height
  384. return true;
  385. }
  386. }
  387. if (parent instanceof Panel || parent instanceof AbstractSplitPanel
  388. || parent instanceof TabSheet
  389. || parent instanceof CustomComponent) {
  390. // height undefined, we know how how component works and no
  391. // exceptions
  392. // TODO horiz SplitPanel ??
  393. return false;
  394. } else {
  395. // We cannot generally know if undefined component can serve
  396. // space for children (like CustomLayout or component built by
  397. // third party) so we assume they can
  398. return true;
  399. }
  400. } else if (hasRelativeHeight(parent)) {
  401. // Relative height
  402. if (parent.getParent() != null) {
  403. return parentCanDefineHeight(parent);
  404. } else {
  405. return true;
  406. }
  407. } else {
  408. // Absolute height
  409. return true;
  410. }
  411. }
  412. private static boolean hasRelativeHeight(Component component) {
  413. return (component.getHeightUnits() == Unit.PERCENTAGE && component
  414. .getHeight() > 0);
  415. }
  416. private static boolean hasNonRelativeWidthComponent(AbstractOrderedLayout ol) {
  417. Iterator<Component> it = ol.getComponentIterator();
  418. while (it.hasNext()) {
  419. if (!hasRelativeWidth(it.next())) {
  420. return true;
  421. }
  422. }
  423. return false;
  424. }
  425. private static boolean hasRelativeWidth(Component paintable) {
  426. return paintable.getWidth() > 0
  427. && paintable.getWidthUnits() == Unit.PERCENTAGE;
  428. }
  429. public static boolean parentCanDefineWidth(Component component) {
  430. Component parent = component.getParent();
  431. if (parent == null) {
  432. // main window, valid situation
  433. return true;
  434. }
  435. if (parent instanceof Window) {
  436. // Sub window with undefined size has a min-width
  437. return true;
  438. }
  439. if (parent.getWidth() < 0) {
  440. // Undefined width
  441. if (parent instanceof AbstractOrderedLayout) {
  442. AbstractOrderedLayout ol = (AbstractOrderedLayout) parent;
  443. boolean horizontal = true;
  444. if (ol instanceof VerticalLayout) {
  445. horizontal = false;
  446. }
  447. if (!horizontal && hasNonRelativeWidthComponent(ol)) {
  448. // valid situation, other components defined width
  449. return true;
  450. } else {
  451. return false;
  452. }
  453. } else if (parent instanceof GridLayout) {
  454. GridLayout gl = (GridLayout) parent;
  455. Area componentArea = gl.getComponentArea(component);
  456. boolean columnHasWidth = false;
  457. for (int col = componentArea.getColumn1(); !columnHasWidth
  458. && col <= componentArea.getColumn2(); col++) {
  459. for (int row = 0; !columnHasWidth && row < gl.getRows(); row++) {
  460. Component c = gl.getComponent(col, row);
  461. if (c != null) {
  462. columnHasWidth = !hasRelativeWidth(c);
  463. }
  464. }
  465. }
  466. if (!columnHasWidth) {
  467. return false;
  468. } else {
  469. // Other components define column width
  470. return true;
  471. }
  472. } else if (parent instanceof Form) {
  473. /*
  474. * If some other part of the form is not relative it determines
  475. * the component width
  476. */
  477. return hasNonRelativeWidthComponent((Form) parent);
  478. } else if (parent instanceof AbstractSplitPanel
  479. || parent instanceof TabSheet
  480. || parent instanceof CustomComponent) {
  481. // FIXME Could we use com.vaadin package name here and
  482. // fail for all component containers?
  483. // FIXME Actually this should be moved to containers so it can
  484. // be implemented for custom containers
  485. // TODO vertical splitpanel with another non relative component?
  486. return false;
  487. } else if (parent instanceof Window) {
  488. // Sub window can define width based on caption
  489. if (parent.getCaption() != null
  490. && !parent.getCaption().equals("")) {
  491. return true;
  492. } else {
  493. return false;
  494. }
  495. } else if (parent instanceof Panel) {
  496. // TODO Panel should be able to define width based on caption
  497. return false;
  498. } else {
  499. return true;
  500. }
  501. } else if (hasRelativeWidth(parent)) {
  502. // Relative width
  503. if (parent.getParent() == null) {
  504. return true;
  505. }
  506. return parentCanDefineWidth(parent);
  507. } else {
  508. return true;
  509. }
  510. }
  511. private static boolean hasNonRelativeWidthComponent(Form form) {
  512. Layout layout = form.getLayout();
  513. Layout footer = form.getFooter();
  514. if (layout != null && !hasRelativeWidth(layout)) {
  515. return true;
  516. }
  517. if (footer != null && !hasRelativeWidth(footer)) {
  518. return true;
  519. }
  520. return false;
  521. }
  522. private static Map<Object, FileLocation> creationLocations = new HashMap<Object, FileLocation>();
  523. private static Map<Object, FileLocation> widthLocations = new HashMap<Object, FileLocation>();
  524. private static Map<Object, FileLocation> heightLocations = new HashMap<Object, FileLocation>();
  525. public static class FileLocation implements Serializable {
  526. public String method;
  527. public String file;
  528. public String className;
  529. public String classNameSimple;
  530. public int lineNumber;
  531. public FileLocation(StackTraceElement traceElement) {
  532. file = traceElement.getFileName();
  533. className = traceElement.getClassName();
  534. classNameSimple = className
  535. .substring(className.lastIndexOf('.') + 1);
  536. lineNumber = traceElement.getLineNumber();
  537. method = traceElement.getMethodName();
  538. }
  539. }
  540. public static void setCreationLocation(Object object) {
  541. setLocation(creationLocations, object);
  542. }
  543. public static void setWidthLocation(Object object) {
  544. setLocation(widthLocations, object);
  545. }
  546. public static void setHeightLocation(Object object) {
  547. setLocation(heightLocations, object);
  548. }
  549. private static void setLocation(Map<Object, FileLocation> map, Object object) {
  550. StackTraceElement[] traceLines = Thread.currentThread().getStackTrace();
  551. for (StackTraceElement traceElement : traceLines) {
  552. Class<?> cls;
  553. try {
  554. String className = traceElement.getClassName();
  555. if (className.startsWith("java.")
  556. || className.startsWith("sun.")) {
  557. continue;
  558. }
  559. cls = Class.forName(className);
  560. if (cls == ComponentSizeValidator.class || cls == Thread.class) {
  561. continue;
  562. }
  563. if (Component.class.isAssignableFrom(cls)
  564. && !CustomComponent.class.isAssignableFrom(cls)) {
  565. continue;
  566. }
  567. FileLocation cl = new FileLocation(traceElement);
  568. map.put(object, cl);
  569. return;
  570. } catch (Exception e) {
  571. // TODO Auto-generated catch block
  572. logger.log(Level.FINER,
  573. "An exception occurred while validating sizes.", e);
  574. }
  575. }
  576. }
  577. }