diff options
author | Mika Murtojarvi <mika@vaadin.com> | 2014-12-03 17:53:02 +0200 |
---|---|---|
committer | Artur Signell <artur@vaadin.com> | 2014-12-04 14:47:21 +0000 |
commit | be35a9b3f1250d9139baf5eeb3f48cb4a20c2822 (patch) | |
tree | d42906cf8bfc825fd060a4fc55ef2e61b5dfffc2 /server/src/com/vaadin | |
parent | ccbed3fcfa5870c376014036bde8455aaa952384 (diff) | |
download | vaadin-framework-be35a9b3f1250d9139baf5eeb3f48cb4a20c2822.tar.gz vaadin-framework-be35a9b3f1250d9139baf5eeb3f48cb4a20c2822.zip |
Fixes for the public API of DesignContext.
Also adds tests for parsing and for finding components by id.
Change-Id: I3202a19f1699ee906f97cc57b08a9b2fd540f51b
Diffstat (limited to 'server/src/com/vaadin')
6 files changed, 128 insertions, 148 deletions
diff --git a/server/src/com/vaadin/ui/AbstractOrderedLayout.java b/server/src/com/vaadin/ui/AbstractOrderedLayout.java index 139a4eb545..87b2ff6f48 100644 --- a/server/src/com/vaadin/ui/AbstractOrderedLayout.java +++ b/server/src/com/vaadin/ui/AbstractOrderedLayout.java @@ -541,7 +541,6 @@ public abstract class AbstractOrderedLayout extends AbstractLayout implements DesignSynchronizable childComponent = (DesignSynchronizable) child; Element childNode = designContext.createNode(childComponent); designElement.appendChild(childNode); - childComponent.synchronizeToDesign(childNode, designContext); // handle alignment Alignment alignment = getComponentAlignment(child); if (alignment.isMiddle()) { diff --git a/server/src/com/vaadin/ui/declarative/ComponentInstantiationException.java b/server/src/com/vaadin/ui/declarative/ComponentInstantiationException.java deleted file mode 100644 index 5a8ec05d87..0000000000 --- a/server/src/com/vaadin/ui/declarative/ComponentInstantiationException.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.vaadin.ui.declarative; - -@SuppressWarnings("serial") -public class ComponentInstantiationException extends RuntimeException { - - public ComponentInstantiationException() { - super(); - } - - public ComponentInstantiationException(String message) { - super(message); - } - - public ComponentInstantiationException(String message, Throwable e) { - super(message, e); - } - -} diff --git a/server/src/com/vaadin/ui/declarative/DesignContext.java b/server/src/com/vaadin/ui/declarative/DesignContext.java index 961affd5b0..a846edff2c 100644 --- a/server/src/com/vaadin/ui/declarative/DesignContext.java +++ b/server/src/com/vaadin/ui/declarative/DesignContext.java @@ -31,7 +31,9 @@ import com.vaadin.ui.DesignSynchronizable; /** * This class contains contextual information that is collected when a component - * tree is constructed based on HTML design template. + * tree is constructed based on HTML design template. This information includes + * mappings from local ids, global ids and captions to components , as well as a + * mapping between prefixes and package names (such as "v" -> "com.vaadin.ui"). * * @since 7.4 * @author Vaadin Ltd @@ -48,12 +50,12 @@ public class DesignContext { // id public static final String ID_ATTRIBUTE = "id"; public static final String CAPTION_ATTRIBUTE = "caption"; - public static final String LOCAL_ID_PREFIX = "_"; - // Mappings from IDs to components. Modified when synchronizing from design. - private Map<String, DesignSynchronizable> globalIdToComponent = new HashMap<String, DesignSynchronizable>(); + public static final String LOCAL_ID_ATTRIBUTE = "_id"; + // Mappings from ids to components. Modified when synchronizing from design. + private Map<String, DesignSynchronizable> idToComponent = new HashMap<String, DesignSynchronizable>(); private Map<String, DesignSynchronizable> localIdToComponent = new HashMap<String, DesignSynchronizable>(); private Map<String, DesignSynchronizable> captionToComponent = new HashMap<String, DesignSynchronizable>(); - // Mapping from components to local IDs. Accessed when synchronizing to + // Mapping from components to local ids. Accessed when synchronizing to // design. Modified when synchronizing from design. private Map<DesignSynchronizable, String> componentToLocalId = new HashMap<DesignSynchronizable, String>(); private Document doc; // required for calling createElement(String) @@ -80,36 +82,73 @@ public class DesignContext { } /** + * Returns a component having the specified local id. If no component is + * found, returns null. + * + * @param localId + * The local id of the component + * @return a component whose local id equals localId + */ + public DesignSynchronizable getComponentByLocalId(String localId) { + return localIdToComponent.get(localId); + } + + /** + * Returns a component having the specified global id. If no component is + * found, returns null. + * + * @param globalId + * The global id of the component + * @return a component whose global id equals globalId + */ + public DesignSynchronizable getComponentById(String globalId) { + return idToComponent.get(globalId); + } + + /** + * Returns a component having the specified caption. If no component is + * found, returns null. + * + * @param caption + * The caption of the component + * @return a component whose caption equals the caption given as a parameter + */ + public DesignSynchronizable getComponentByCaption(String caption) { + return captionToComponent.get(caption); + } + + /** * Creates a mapping between the given global id and the component. Returns - * true if globalId was already mapped to some component or if component was - * mapped to some string. Otherwise returns false. Also sets the id of the - * component to globalId. + * true if globalId was already mapped to some component. Otherwise returns + * false. Also sets the id of the component to globalId. + * + * If there is a mapping from the component to a global id (gid) different + * from globalId, the mapping from gid to component is removed. * * If the string was mapped to a component c different from the given * component, the mapping from c to the string is removed. Similarly, if * component was mapped to some string s different from globalId, the * mapping from s to component is removed. * - * @since * @param globalId * The new global id of the component. * @param component * The component whose global id is to be set. * @return true, if there already was a global id mapping from the string to - * some component or from the component to some string. Otherwise - * returns false. + * some component. */ - public boolean mapGlobalId(String globalId, DesignSynchronizable component) { - DesignSynchronizable oldComponent = globalIdToComponent.get(globalId); + private boolean mapId(String globalId, DesignSynchronizable component) { + DesignSynchronizable oldComponent = idToComponent.get(globalId); if (oldComponent != null && !oldComponent.equals(component)) { oldComponent.setId(null); } String oldGID = component.getId(); if (oldGID != null && !oldGID.equals(globalId)) { - globalIdToComponent.remove(oldGID); + idToComponent.remove(oldGID); } component.setId(globalId); - return oldComponent != null || oldGID != null; + idToComponent.put(globalId, component); + return oldComponent != null && !oldComponent.equals(component); } /** @@ -122,8 +161,7 @@ public class DesignContext { * component was mapped to some string s different from localId, the mapping * from s to component is removed. * - * @since - * @param globalId + * @param localId * The new local id of the component. * @param component * The component whose local id is to be set. @@ -131,7 +169,7 @@ public class DesignContext { * some component or from the component to some string. Otherwise * returns false. */ - public boolean mapLocalId(String localId, DesignSynchronizable component) { + private boolean mapLocalId(String localId, DesignSynchronizable component) { return twoWayMap(localId, component, localIdToComponent, componentToLocalId); } @@ -146,7 +184,6 @@ public class DesignContext { * a given caption can be found using the map captionToComponent. Hence, any * captions that are used to identify an object should be unique. * - * @since * @param caption * The new caption of the component. * @param component @@ -154,7 +191,7 @@ public class DesignContext { * @return true, if there already was a caption mapping from the string to * some component. */ - public boolean mapCaption(String caption, DesignSynchronizable component) { + private boolean mapCaption(String caption, DesignSynchronizable component) { return captionToComponent.put(caption, component) != null; } @@ -168,7 +205,6 @@ public class DesignContext { * Returns true if there already was a mapping from key to some value v or * if there was a mapping from value to some key k. Otherwise returns false. * - * @since * @param key * The new key in keyToValue. * @param value @@ -198,7 +234,6 @@ public class DesignContext { * true if prefix was already mapped to some package name or packageName to * some prefix. * - * @since * @param prefix * the prefix name without an ending dash (for instance, "v" is * always used for "com.vaadin.ui") @@ -207,7 +242,7 @@ public class DesignContext { * @return whether there was a mapping from prefix to some package name or * from packageName to some prefix. */ - public boolean mapPrefixToPackage(String prefix, String packageName) { + private boolean mapPrefixToPackage(String prefix, String packageName) { return twoWayMap(prefix, packageName, prefixToPackage, packageToPrefix); } @@ -240,7 +275,6 @@ public class DesignContext { * Get and store the mappings from prefixes to package names from meta tags * located under <head> in the html document. * - * @since */ public void getPrefixes(Document doc) { Element head = doc.head(); @@ -258,7 +292,7 @@ public class DesignContext { String contentString = attributes.get("content"); String[] parts = contentString.split(":"); if (parts.length != 2) { - throw new LayoutInflaterException("The meta tag '" + throw new DesignException("The meta tag '" + child.toString() + "' cannot be parsed."); } String prefixName = parts[0]; @@ -297,7 +331,6 @@ public class DesignContext { * the synchronizeToDesign() call, this method creates the entire subtree * rooted at the returned Node. * - * @since * @param childComponent * A component implementing the DesignSynchronizable interface. * @return An html tree node corresponding to the given component. The tag @@ -321,8 +354,7 @@ public class DesignContext { // care of by synchronizeToDesign. String localId = componentToLocalId.get(childComponent); if (localId != null) { - localId = LOCAL_ID_PREFIX + localId; - newElement.attr(localId, ""); + newElement.attr(LOCAL_ID_ATTRIBUTE, localId); } return newElement; } @@ -333,7 +365,6 @@ public class DesignContext { * inserting a dash before the letter. No dash is inserted before the first * letter of the class name. * - * @since * @param className * the name of the class without a package name * @return the html tag name corresponding to className @@ -359,7 +390,6 @@ public class DesignContext { * node. Also calls synchronizeFromDesign() for the created node, in effect * creating the entire component hierarchy rooted at the returned component. * - * @since * @param componentDesign * The html tree node containing the description of the component * to be created. @@ -370,16 +400,16 @@ public class DesignContext { // Create the component. DesignSynchronizable component = instantiateComponent(componentDesign); component.synchronizeFromDesign(componentDesign, this); - // Get the IDs and the caption of the component and store them in the + // Get the ids and the caption of the component and store them in the // maps of this design context. org.jsoup.nodes.Attributes attributes = componentDesign.attributes(); // global id: only update the mapping, the id has already been set for // the component - String id = component.getCaption(); + String id = component.getId(); if (id != null && id.length() > 0) { - boolean mappingExists = mapGlobalId(id, component); + boolean mappingExists = mapId(id, component); if (mappingExists) { - throw new LayoutInflaterException( + throw new DesignException( "The following global id is not unique: " + id); } } @@ -387,17 +417,12 @@ public class DesignContext { // from the attributes of componentDesign String localId = null; for (Attribute attribute : attributes.asList()) { - if (attribute.getKey().startsWith(LOCAL_ID_PREFIX)) { + if (attribute.getKey().equals(LOCAL_ID_ATTRIBUTE)) { if (localId != null) { - throw new LayoutInflaterException( - "Duplicate local ids specified: " - + localId - + " and " - + attribute.getKey().substring( - LOCAL_ID_PREFIX.length())); + throw new DesignException("Duplicate local ids specified: " + + localId + "."); } - localId = attribute.getKey() - .substring(LOCAL_ID_PREFIX.length()); + localId = attribute.getValue(); mapLocalId(localId, component); // two-way map } } @@ -413,7 +438,6 @@ public class DesignContext { * Creates a DesignSynchronizable component corresponding to the given node. * Does not set the attributes for the created object. * - * @since * @param node * a node of an html tree * @return a DesignSynchronizable object corresponding to node, with no @@ -427,16 +451,15 @@ public class DesignContext { DesignSynchronizable newComponent = componentClass.newInstance(); return newComponent; } catch (Exception e) { - throw createException(e, qualifiedClassName); + throw new DesignException("No component class could be found for " + + node.nodeName() + ".", e); } } /** * Returns the qualified class name corresponding to the given html tree - * node. If the node is not a span or a div, the class name is extracted - * from the tag name of node. + * node. The class name is extracted from the tag name of node. * - * @since * @param node * an html tree node * @return The qualified class name corresponding to the given node. @@ -445,22 +468,19 @@ public class DesignContext { String tagName = node.nodeName(); if (tagName.equals("v-addon")) { return node.attr("class"); - } else if (tagName.toLowerCase(Locale.ENGLISH).equals("span") - || tagName.toLowerCase(Locale.ENGLISH).equals("div")) { - return "com.vaadin.ui.Label"; } // Otherwise, get the full class name using the prefix to package // mapping. Example: "v-vertical-layout" -> // "com.vaadin.ui.VerticalLayout" String[] parts = tagName.split("-"); if (parts.length < 2) { - throw new LayoutInflaterException("The tagname '" + tagName + throw new DesignException("The tagname '" + tagName + "' is invalid: missing prefix."); } String prefixName = parts[0]; String packageName = prefixToPackage.get(prefixName); if (packageName == null) { - throw new LayoutInflaterException("Unknown tag: " + tagName); + throw new DesignException("Unknown tag: " + tagName); } int firstCharacterIndex = prefixName.length() + 1; // +1 is for '-' tagName = tagName.substring(firstCharacterIndex, @@ -498,20 +518,6 @@ public class DesignContext { } } - /* - * Create a new ComponentInstantiationException. - */ - private ComponentInstantiationException createException(Exception e, - String qualifiedClassName) { - String message = String.format( - "Couldn't instantiate a component for %s.", qualifiedClassName); - if (e != null) { - return new ComponentInstantiationException(message, e); - } else { - return new ComponentInstantiationException(message); - } - } - /** * Returns {@code true} if the given {@link Class} implements the * {@link Component} interface of Vaadin Framework otherwise {@code false}. @@ -532,7 +538,6 @@ public class DesignContext { /** * Returns the root component of a created component hierarchy. * - * @since * @return */ public DesignSynchronizable getComponentRoot() { @@ -545,4 +550,4 @@ public class DesignContext { public void setComponentRoot(DesignSynchronizable componentRoot) { this.componentRoot = componentRoot; } -}
\ No newline at end of file +} diff --git a/server/src/com/vaadin/ui/declarative/DesignException.java b/server/src/com/vaadin/ui/declarative/DesignException.java new file mode 100644 index 0000000000..74bcd4cdc6 --- /dev/null +++ b/server/src/com/vaadin/ui/declarative/DesignException.java @@ -0,0 +1,18 @@ +package com.vaadin.ui.declarative; + +@SuppressWarnings("serial") +public class DesignException extends RuntimeException { + + public DesignException() { + super(); + } + + public DesignException(String message) { + super(message); + } + + public DesignException(String message, Throwable e) { + super(message, e); + } + +} diff --git a/server/src/com/vaadin/ui/declarative/LayoutHandler.java b/server/src/com/vaadin/ui/declarative/LayoutHandler.java index 045fb43b2c..84d454bb01 100644 --- a/server/src/com/vaadin/ui/declarative/LayoutHandler.java +++ b/server/src/com/vaadin/ui/declarative/LayoutHandler.java @@ -16,11 +16,8 @@ package com.vaadin.ui.declarative; import java.io.BufferedWriter; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStreamWriter; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; @@ -38,18 +35,17 @@ import com.vaadin.ui.DesignSynchronizable; * component hierarchy must contain a single root. * * - * @since + * @since 7.4 * @author Vaadin Ltd */ public class LayoutHandler { /** * Constructs a component hierarchy from the design specified as an html - * document. The 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 + * 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. * - * @since * @param html * the html document describing the component design * @return the DesignContext created while traversing the tree. The @@ -63,9 +59,37 @@ public class LayoutHandler { try { doc = Jsoup.parse(html, "UTF-8", "", Parser.htmlParser()); } catch (IOException e) { - throw new LayoutInflaterException( - "The html document cannot be parsed."); + throw new DesignException("The html document cannot be parsed."); } + return parse(doc); + } + + /** + * Constructs a component hierarchy from the design specified as an html + * 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. + * + * @param html + * the html document describing the component 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(String html) { + Document doc = Jsoup.parse(html); + return parse(doc); + } + + /** + * Constructs a component hierarchy from the design specified as an html + * tree. + * + */ + private static DesignContext parse(Document doc) { DesignContext designContext = new DesignContext(doc); designContext.getPrefixes(doc); // No special handling for a document without a body element - should be @@ -75,7 +99,7 @@ public class LayoutHandler { for (Node element : root.childNodes()) { if (element instanceof Element) { if (componentRoot != null) { - throw new LayoutInflaterException( + throw new DesignException( "The first level of a component hierarchy should contain a single root component, but found " + "two: " + componentRoot @@ -96,7 +120,6 @@ public class LayoutHandler { * the tree. The generated tree corresponds to a valid html document. * * - * @since * @param root * the root of the component hierarchy * @return an html tree representation of the component hierarchy @@ -126,7 +149,6 @@ public class LayoutHandler { * Generates an html file corresponding to the component hierarchy with the * given root. * - * @since * @param writer * @param root * @throws IOException @@ -136,32 +158,4 @@ public class LayoutHandler { String docAsString = createHtml(ctx).toString(); writer.write(docAsString); } - - /** - * Used for testing only. - * - * This method reads and constructs a component hierarchy from a file whose - * name is given as the first parameter. The constructed hierarchy is then - * transformed back to html form, the resulting html being written to a file - * whose name is given as the second parameter. - * - * This is useful for checking that: 1) a design can be successfully parsed - * 2) a component hierarchy can be transformed to html form and 3) No - * relevant information is lost in these conversions. - * - */ - public static void main(String[] args) throws IOException { - String inputFileName = args[0]; - String outputFileName = args[1]; - // Read and parse the contents of the output file. - FileInputStream fis = new FileInputStream(inputFileName); - DesignContext ctx = parse(fis); - DesignSynchronizable root = ctx.getComponentRoot(); - // Write - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(outputFileName))); - createHtml(writer, ctx); - fis.close(); - writer.close(); - } -}
\ No newline at end of file +} diff --git a/server/src/com/vaadin/ui/declarative/LayoutInflaterException.java b/server/src/com/vaadin/ui/declarative/LayoutInflaterException.java deleted file mode 100644 index 3482856645..0000000000 --- a/server/src/com/vaadin/ui/declarative/LayoutInflaterException.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.vaadin.ui.declarative; - -@SuppressWarnings("serial") -public class LayoutInflaterException extends RuntimeException { - - public LayoutInflaterException(String message) { - super(message); - } - - public LayoutInflaterException(String message, Throwable e) { - super(message, e); - } - - public LayoutInflaterException(Throwable e) { - super(e); - } - -} |