diff options
author | Matti Hosio <mhosio@vaadin.com> | 2014-12-05 11:27:29 +0200 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2014-12-08 18:19:47 +0000 |
commit | 18b333ee3dfd171956829e97a32baa8069346c65 (patch) | |
tree | d02139df7b7843b06b70629c35c7954d689826af | |
parent | bd78a4c8b2eb84cbba3bc686bcb40b19fd847640 (diff) | |
download | vaadin-framework-18b333ee3dfd171956829e97a32baa8069346c65.tar.gz vaadin-framework-18b333ee3dfd171956829e97a32baa8069346c65.zip |
Support for automatic binding of the fields of the root layout (#7749)
The fields are bound to the components generated when parsing the design. The binding is done based on localId, id or caption.
Change-Id: I32ecac3cb76737c9d9d05a123db85fe65d55369c
7 files changed, 670 insertions, 20 deletions
diff --git a/server/src/com/vaadin/ui/declarative/DesignContext.java b/server/src/com/vaadin/ui/declarative/DesignContext.java index a846edff2c..36d0162910 100644 --- a/server/src/com/vaadin/ui/declarative/DesignContext.java +++ b/server/src/com/vaadin/ui/declarative/DesignContext.java @@ -15,8 +15,10 @@ */ package com.vaadin.ui.declarative; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; @@ -67,6 +69,9 @@ public class DesignContext { // to "com.vaadin.ui". private Map<String, String> defaultPrefixes = new HashMap<String, String>(); + // component creation listeners + private List<ComponentCreationListener> listeners = new ArrayList<ComponentCreationListener>(); + public DesignContext(Document doc) { this.doc = doc; // Initialize the mapping between prefixes and package names. @@ -393,12 +398,31 @@ public class DesignContext { * @param componentDesign * The html tree node containing the description of the component * to be created. - * @return a DesignSynchronizable object corresponding to componentDesign, - * with no attributes set. + * @return a DesignSynchronizable object corresponding to componentDesign */ public DesignSynchronizable createChild(Element componentDesign) { // Create the component. DesignSynchronizable component = instantiateComponent(componentDesign); + synchronizeAndRegister(component, componentDesign); + fireComponentCreatedEvent(componentToLocalId.get(component), component); + return component; + } + + /** + * Calls synchronizeFromDesign() for the given component and passes the + * given component design as a parameter. This creates the entire component + * hierarchy rooted at the given component. Also registers the componentid, + * localId and caption of the given component and all its children to the + * context + * + * + * @param component + * The component to be synchronized from design + * @param componentDesign + * The html tree node containing the description of the component + */ + public void synchronizeAndRegister(DesignSynchronizable component, + Element componentDesign) { component.synchronizeFromDesign(componentDesign, this); // Get the ids and the caption of the component and store them in the // maps of this design context. @@ -431,7 +455,6 @@ public class DesignContext { if (caption != null) { mapCaption(caption, component); } - return component; } /** @@ -550,4 +573,106 @@ public class DesignContext { public void setComponentRoot(DesignSynchronizable componentRoot) { this.componentRoot = componentRoot; } + + /** + * Adds a component creation listener. The listener will be notified when + * components are created while parsing a design template + * + * @param listener + * the component creation listener to be added + */ + public void addComponentCreationListener(ComponentCreationListener listener) { + listeners.add(listener); + } + + /** + * Removes a component creation listener. + * + * @param listener + * the component creation listener to be removed + */ + public void removeComponentCreationListener( + ComponentCreationListener listener) { + listeners.remove(listener); + } + + /** + * Fires component creation event + * + * @param localId + * localId of the component + * @param component + * the component that was created + */ + private void fireComponentCreatedEvent(String localId, + DesignSynchronizable component) { + ComponentCreatedEvent event = new ComponentCreatedEvent(localId, + component); + for (ComponentCreationListener listener : listeners) { + listener.componentCreated(event); + } + } + + /** + * Interface to be implemented by component creation listeners + * + * @author Vaadin Ltd + */ + public interface ComponentCreationListener { + + /** + * Called when component has been created in the design context + * + * @param event + * the component creation event containing information on the + * created component + */ + public void componentCreated(ComponentCreatedEvent event); + } + + /** + * Component creation event that is fired when a component is created in the + * context + * + * @author Vaadin Ltd + */ + public class ComponentCreatedEvent { + private String localId; + private DesignSynchronizable component; + private DesignContext context; + + /** + * Creates a new instance of ComponentCreatedEvent + * + * @param localId + * the local id of the created component + * @param component + * the created component + */ + private ComponentCreatedEvent(String localId, + DesignSynchronizable component) { + this.localId = localId; + this.component = component; + context = DesignContext.this; + } + + /** + * Returns the local id of the created component or null if not exist + * + * @return the localId + */ + public String getLocalId() { + return localId; + } + + /** + * Returns the created component + * + * @return the component + */ + public DesignSynchronizable getComponent() { + return component; + } + } + } diff --git a/server/src/com/vaadin/ui/declarative/FieldBinder.java b/server/src/com/vaadin/ui/declarative/FieldBinder.java new file mode 100644 index 0000000000..c533af15bd --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/FieldBinder.java @@ -0,0 +1,247 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui.declarative; + +import java.beans.IntrospectionException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import com.vaadin.ui.Component; +import com.vaadin.util.ReflectTools; + +/** + * Binder utility that binds member fields of a design class instance to given + * component instances. Only fields of type {@link Component} are bound + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class FieldBinder { + + // the design class instance (the instance containing the bound fields) + private Component bindTarget; + // mapping between field names and Fields + private Map<String, Field> fieldMap = new HashMap<String, Field>(); + + /** + * Creates a new instance of LayoutFieldBinder + * + * @param design + * the design class instance containing the bound fields + * @throws IntrospectionException + * if the given design class can not be introspected + */ + public FieldBinder(Component design) throws IntrospectionException { + if (design == null) { + throw new IllegalArgumentException("The design must not be null"); + } + bindTarget = design; + resolveFields(); + } + + /** + * Returns true if all the fields assignable to Component are bound + * + * @return true if all the fields assignable to Component are bound + */ + public boolean isAllFieldsBound() throws FieldBindingException { + boolean success = true; + for (Field f : fieldMap.values()) { + try { + Object value = ReflectTools.getJavaFieldValue(bindTarget, f); + if (value == null) { + success = false; + getLogger().severe("Found unbound field :" + f.getName()); + } + } catch (IllegalArgumentException e) { + throw new FieldBindingException("Could not get field value", e); + } catch (IllegalAccessException e) { + throw new FieldBindingException("Could not get field value", e); + } catch (InvocationTargetException e) { + throw new FieldBindingException("Could not get field value", e); + } + } + return success; + } + + /** + * Resolves the fields of the design class instance + */ + private void resolveFields() { + for (Field memberField : getFieldsInDeclareOrder(bindTarget.getClass())) { + if (Component.class.isAssignableFrom(memberField.getType())) { + fieldMap.put(memberField.getName(), memberField); + } + } + } + + /** + * Tries to bind the given {@link Component} instance to a member field of + * the bind target. The name of the bound field is constructed based on the + * id or caption of the instance, depending on which one is defined. If a + * field is already bound (not null), {@link FieldBindingException} is + * thrown. + * + * @param instance + * the instance to be bound to a field + * @return true on success, otherwise false + * @throws FieldBindingException + * if error occurs when trying to bind the instance to a field + */ + public boolean bindField(Component instance) { + return bindField(instance, null); + } + + /** + * Tries to bind the given {@link Component} instance to a member field of + * the bind target. The fields are matched based on localId, id and caption. + * If a field is already bound (not null), {@link FieldBindingException} is + * thrown. + * + * @param instance + * the instance to be bound to a field + * @param localId + * the localId used for mapping the field to an instance field + * @return true on success + * @throws FieldBindingException + * if error occurs when trying to bind the instance to a field + */ + public boolean bindField(Component instance, String localId) { + // check that the field exists, is correct type and is null + boolean success = bindFieldByIdentifier(localId, instance); + if (!success) { + success = bindFieldByIdentifier(instance.getId(), instance); + } + if (!success) { + success = bindFieldByIdentifier(instance.getCaption(), instance); + } + if (!success) { + String idInfo = "localId: " + localId + " id: " + instance.getId() + + " caption: " + instance.getCaption(); + getLogger().info( + "Could not bind component to a field " + + instance.getClass().getName() + " " + idInfo); + } + return success; + } + + /** + * Tries to bind the given {@link Component} instance to a member field of + * the bind target. The field is matched based on the given identifier. If a + * field is already bound (not null), {@link FieldBindingException} is + * thrown. + * + * @param identifier + * the identifier for the field. + * @param instance + * the instance to be bound to a field + * @return true on success + * @throws FieldBindingException + * if error occurs when trying to bind the instance to a field + */ + private boolean bindFieldByIdentifier(String identifier, Component instance) { + try { + // create and validate field name + String fieldName = asFieldName(identifier); + if (fieldName.length() == 0) { + return false; + } + // validate that the field can be found + Field field = fieldMap.get(fieldName); + if (field == null) { + getLogger().fine( + "No field was found by identifier " + identifier); + return false; + } + // validate that the field is not set + Object fieldValue = ReflectTools.getJavaFieldValue(bindTarget, + field); + if (fieldValue != null) { + getLogger().severe( + "The field with identifier \"" + identifier + + "\" already mapped"); + throw new FieldBindingException( + "Duplicate identifier found for a field"); + } + // set the field value + ReflectTools.setJavaFieldValue(bindTarget, field, instance); + return true; + } catch (IllegalAccessException e) { + throw new FieldBindingException("Field binding failed", e); + } catch (IllegalArgumentException e) { + throw new FieldBindingException("Field binding failed", e); + } catch (InvocationTargetException e) { + throw new FieldBindingException("Field binding failed", e); + } + } + + /** + * Converts the given identifier to a valid field name by stripping away + * illegal character and setting the first letter of the name to lowercase + * + * @param identifier + * the identifier to be converted to field name + * @return the field name corresponding the identifier + */ + private static String asFieldName(String identifier) { + if (identifier == null) { + return ""; + } + StringBuilder result = new StringBuilder(); + for (int i = 0; i < identifier.length(); i++) { + char character = identifier.charAt(i); + if (Character.isJavaIdentifierPart(character)) { + result.append(character); + } + } + // lowercase first letter + if (result.length() > 0 && Character.isLetter(result.charAt(0))) { + result.setCharAt(0, Character.toLowerCase(result.charAt(0))); + } + return result.toString(); + } + + /** + * Returns an array containing Field objects reflecting all the fields of + * the class or interface represented by this Class object. The elements in + * the array returned are sorted in declare order. The fields in + * superclasses are excluded. + * + * @param searchClass + * the class to be scanned for fields + * @return the list of fields in this class + */ + protected static List<java.lang.reflect.Field> getFieldsInDeclareOrder( + Class<?> searchClass) { + ArrayList<java.lang.reflect.Field> memberFieldsInOrder = new ArrayList<java.lang.reflect.Field>(); + + for (java.lang.reflect.Field memberField : searchClass + .getDeclaredFields()) { + memberFieldsInOrder.add(memberField); + } + return memberFieldsInOrder; + } + + private static Logger getLogger() { + return Logger.getLogger(FieldBinder.class.getName()); + } + +} diff --git a/server/src/com/vaadin/ui/declarative/FieldBindingException.java b/server/src/com/vaadin/ui/declarative/FieldBindingException.java new file mode 100644 index 0000000000..d8b587a14c --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/FieldBindingException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.ui.declarative; + +/** + * Exception that is thrown when an error occurs during field binding when + * reading a design template + * + * @since 7.4 + * @author Vaadin Ltd + */ +public class FieldBindingException extends RuntimeException { + + public FieldBindingException(String message) { + super(message); + } + + public FieldBindingException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/server/src/com/vaadin/ui/declarative/LayoutHandler.java b/server/src/com/vaadin/ui/declarative/LayoutHandler.java index 84d454bb01..c2efe8eb32 100644 --- a/server/src/com/vaadin/ui/declarative/LayoutHandler.java +++ b/server/src/com/vaadin/ui/declarative/LayoutHandler.java @@ -15,6 +15,7 @@ */ package com.vaadin.ui.declarative; +import java.beans.IntrospectionException; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; @@ -25,8 +26,11 @@ import org.jsoup.nodes.DocumentType; import org.jsoup.nodes.Element; import org.jsoup.nodes.Node; import org.jsoup.parser.Parser; +import org.jsoup.select.Elements; import com.vaadin.ui.DesignSynchronizable; +import com.vaadin.ui.declarative.DesignContext.ComponentCreatedEvent; +import com.vaadin.ui.declarative.DesignContext.ComponentCreationListener; /** * LayoutHandler is used for parsing a component hierarchy from an html file @@ -61,7 +65,39 @@ public class LayoutHandler { } catch (IOException e) { throw new DesignException("The html document cannot be parsed."); } - return parse(doc); + return parse(doc, null); + } + + /** + * Constructs a component hierarchy from the design specified as an html + * document. The component hierarchy must contain exactly one top-level + * Component. The component should be located under <body>, but also invalid + * html containing the hierarchy without <html>, <head> and <body> tags is + * accepted. You can optionally pass instance for the root component with + * some uninitialized instance fields. The fields will be automatically + * populated when parsing the design based on the component ids, local ids, + * and captions of the components in the design. + * + * @param html + * the html document describing the component design + * @param rootInstance + * the root instance with fields to be mapped to components in + * the design + * @return the DesignContext created while traversing the tree. The + * top-level component of the created component hierarchy can be + * accessed using result.getRootComponent(), where result is the + * object returned by this method. + * @throws IOException + */ + public static DesignContext parse(InputStream html, + DesignSynchronizable rootInstance) { + Document doc; + try { + doc = Jsoup.parse(html, "UTF-8", "", Parser.htmlParser()); + } catch (IOException e) { + throw new DesignException("The html document cannot be parsed."); + } + return parse(doc, rootInstance); } /** @@ -81,36 +117,100 @@ public class LayoutHandler { */ public static DesignContext parse(String html) { Document doc = Jsoup.parse(html); - return parse(doc); + return parse(doc, null); } /** * Constructs a component hierarchy from the design specified as an html - * tree. + * document given as a string. The component hierarchy must contain exactly + * one top-level Component. The component should be located under <body>, + * but also invalid html containing the hierarchy without <html>, <head> and + * <body> tags is accepted. You can optionally pass instance for the root + * component with some uninitialized instance fields. The fields will be + * automatically populated when parsing the design based on the component + * ids, local ids, and captions of the components in the design. * + * @param html + * the html document describing the component design + * @param rootInstance + * the root instance with fields to be mapped to components in + * the design + * @return the DesignContext created while traversing the tree. The + * top-level component of the created component hierarchy can be + * accessed using result.getRootComponent(), where result is the + * object returned by this method. + * @throws IOException */ - private static DesignContext parse(Document doc) { + public static DesignContext parse(String html, + DesignSynchronizable rootInstance) { + Document doc = Jsoup.parse(html); + return parse(doc, rootInstance); + } + + /** + * Constructs a component hierarchy from the design specified as an html + * tree. If componentRoot is not null, the component instances created + * during synchronizing the design are assigned to its member fields based + * on their id, localId, and caption + * + * @param doc + * the html tree + * @param componentRoot + * optional component root instance with some member fields. The + * type must match the type of the root element in the design. + * The member fields whose type is assignable from + * {@link DesignSynchronizable} are set when parsing the + * component tree + * + */ + private static DesignContext parse(Document doc, + DesignSynchronizable componentRoot) { DesignContext designContext = new DesignContext(doc); designContext.getPrefixes(doc); // No special handling for a document without a body element - should be // taken care of by jsoup. Element root = doc.body(); - DesignSynchronizable componentRoot = null; - for (Node element : root.childNodes()) { - if (element instanceof Element) { - if (componentRoot != null) { - throw new DesignException( - "The first level of a component hierarchy should contain a single root component, but found " - + "two: " - + componentRoot - + " and " - + element + "."); + Elements children = root.children(); + if (children.size() != 1) { + throw new DesignException( + "The first level of a component hierarchy should contain exactly one root component, but found " + + children.size()); + } + Element element = children.first(); + if (componentRoot != null) { + // user has specified root instance that may have member fields that + // should be bound + FieldBinder binder = null; + try { + binder = new FieldBinder(componentRoot); + } catch (IntrospectionException e) { + throw new DesignException( + "Could not bind fields of the root component", e); + } + final FieldBinder fBinder = binder; + // create listener for component creations that binds the created + // components to the componentRoot instance fields + ComponentCreationListener creationListener = new ComponentCreationListener() { + @Override + public void componentCreated(ComponentCreatedEvent event) { + fBinder.bindField(event.getComponent(), event.getLocalId()); } - // createChild creates the entire component hierarchy - componentRoot = designContext.createChild((Element) element); - designContext.setComponentRoot(componentRoot); + }; + designContext.addComponentCreationListener(creationListener); + // create subtree + designContext.synchronizeAndRegister(componentRoot, element); + // make sure that all the member fields are bound + if (!binder.isAllFieldsBound()) { + throw new DesignException( + "Found unbound fields from component root"); } + // no need to listen anymore + designContext.removeComponentCreationListener(creationListener); + } else { + // createChild creates the entire component hierarchy + componentRoot = designContext.createChild(element); } + designContext.setComponentRoot(componentRoot); return designContext; } diff --git a/server/tests/src/com/vaadin/tests/layoutparser/InvalidLayoutTemplate.java b/server/tests/src/com/vaadin/tests/layoutparser/InvalidLayoutTemplate.java new file mode 100644 index 0000000000..9447acb37e --- /dev/null +++ b/server/tests/src/com/vaadin/tests/layoutparser/InvalidLayoutTemplate.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.layoutparser; + +import com.vaadin.ui.Button; +import com.vaadin.ui.NativeButton; +import com.vaadin.ui.TextField; +import com.vaadin.ui.VerticalLayout; + +/** + * + * @since + * @author Vaadin Ltd + */ +public class InvalidLayoutTemplate extends VerticalLayout { + private NativeButton firstButton; + private NativeButton secondButton; + private NativeButton yetanotherbutton; // generated based on caption + private Button clickme; // generated based on caption + private TextField shouldNotBeMapped; + + public NativeButton getFirstButton() { + return firstButton; + } + + public NativeButton getSecondButton() { + return secondButton; + } + + public NativeButton getYetanotherbutton() { + return yetanotherbutton; + } + + public Button getClickme() { + return clickme; + } + + public TextField getShouldNotBeMapped() { + return shouldNotBeMapped; + } + +} diff --git a/server/tests/src/com/vaadin/tests/layoutparser/LayoutTemplate.java b/server/tests/src/com/vaadin/tests/layoutparser/LayoutTemplate.java new file mode 100644 index 0000000000..72131826ba --- /dev/null +++ b/server/tests/src/com/vaadin/tests/layoutparser/LayoutTemplate.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.vaadin.tests.layoutparser; + +import com.vaadin.ui.Button; +import com.vaadin.ui.NativeButton; +import com.vaadin.ui.VerticalLayout; + +/** + * Template to be populated in the tests + * + * @since + * @author Vaadin Ltd + */ +public class LayoutTemplate extends VerticalLayout { + private NativeButton firstButton; // assigned based on local id + private NativeButton secondButton; // assigned based on id + private NativeButton yetanotherbutton; // assigned based on caption + private Button clickme; // assigned based on caption + + public NativeButton getFirstButton() { + return firstButton; + } + + public NativeButton getSecondButton() { + return secondButton; + } + + public NativeButton getYetanotherbutton() { + return yetanotherbutton; + } + + public Button getClickme() { + return clickme; + } +} diff --git a/server/tests/src/com/vaadin/tests/layoutparser/ParseLayoutTest.java b/server/tests/src/com/vaadin/tests/layoutparser/ParseLayoutTest.java index adba5e57b9..1c4d50cc55 100644 --- a/server/tests/src/com/vaadin/tests/layoutparser/ParseLayoutTest.java +++ b/server/tests/src/com/vaadin/tests/layoutparser/ParseLayoutTest.java @@ -18,6 +18,7 @@ package com.vaadin.tests.layoutparser; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import junit.framework.TestCase; @@ -35,6 +36,7 @@ import com.vaadin.ui.TextArea; import com.vaadin.ui.TextField; import com.vaadin.ui.VerticalLayout; import com.vaadin.ui.declarative.DesignContext; +import com.vaadin.ui.declarative.DesignException; import com.vaadin.ui.declarative.LayoutHandler; /** @@ -106,6 +108,43 @@ public class ParseLayoutTest extends TestCase { } /* + * Check that the field binding works if root instance with member fields is + * passed to the LayoutHandler + * + * @throws IOException + */ + public void testFieldBinding() throws IOException { + LayoutTemplate template = new LayoutTemplate(); + InputStream htmlFile = new FileInputStream( + "server/tests/src/com/vaadin/tests/layoutparser/testFile.html"); + LayoutHandler.parse(htmlFile, template); + assertNotNull(template.getFirstButton()); + assertNotNull(template.getSecondButton()); + assertNotNull(template.getYetanotherbutton()); + assertNotNull(template.getClickme()); + assertEquals("Native click me", template.getFirstButton().getCaption()); + } + + /* + * Check that the field binding fails if some of the fields in the root + * instance were not bound + * + * @throws IOException + */ + public void testUnboundFields() throws IOException { + InvalidLayoutTemplate template = new InvalidLayoutTemplate(); + InputStream htmlFile = new FileInputStream( + "server/tests/src/com/vaadin/tests/layoutparser/testFile.html"); + try { + LayoutHandler.parse(htmlFile, template); + // we are expecting an exception + fail(); + } catch (DesignException e) { + // expected + } + } + + /* * Checks that the correct components occur in the correct order in the * component hierarchy rooted at context.getComponentRoot(). */ @@ -173,4 +212,5 @@ public class ParseLayoutTest extends TestCase { assertTrue("The found button is incorrect.", thirdButton.getCaption() .equals("Yet another button")); } + } |