您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

ComponentSizeValidator.java 25KB

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