summaryrefslogtreecommitdiffstats
path: root/server/src/com
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2014-12-14 20:01:52 +0200
committerMatti Hosio <mhosio@vaadin.com>2014-12-15 13:20:12 +0200
commit898e28d6aebc2b484f59f528a609bd59e5dfa4f5 (patch)
treecdb0bc9b76af30fb21b631c11e65093c0b5ceeb5 /server/src/com
parenta27ea03db9f6ba5f4358c359645cd62617d2d205 (diff)
downloadvaadin-framework-898e28d6aebc2b484f59f528a609bd59e5dfa4f5.tar.gz
vaadin-framework-898e28d6aebc2b484f59f528a609bd59e5dfa4f5.zip
Add public API for loading design based on @DesignRoot (#7749)
Change-Id: Ic6f201a45d66aefe9ec93ba3be5a75b6532bf014
Diffstat (limited to 'server/src/com')
-rw-r--r--server/src/com/vaadin/annotations/DesignRoot.java41
-rw-r--r--server/src/com/vaadin/ui/declarative/Design.java140
-rw-r--r--server/src/com/vaadin/ui/declarative/DesignContext.java2
-rw-r--r--server/src/com/vaadin/ui/declarative/FieldBinder.java29
4 files changed, 193 insertions, 19 deletions
diff --git a/server/src/com/vaadin/annotations/DesignRoot.java b/server/src/com/vaadin/annotations/DesignRoot.java
new file mode 100644
index 0000000000..a00a00dc0b
--- /dev/null
+++ b/server/src/com/vaadin/annotations/DesignRoot.java
@@ -0,0 +1,41 @@
+/*
+ * 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.vaadin.ui.declarative.Design;
+
+/**
+ * Marks the component as the root of a design (html) file.
+ * <p>
+ * Used together with {@link Design#read(com.vaadin.ui.Component)} to be able
+ * the load the design without further configuration. The design is loaded from
+ * the same package as the annotated class and by default the design filename is
+ * derived from the class name. Using the {@link #value()} parameter you can
+ * specify another design file name.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface DesignRoot {
+ String value() default "";
+}
diff --git a/server/src/com/vaadin/ui/declarative/Design.java b/server/src/com/vaadin/ui/declarative/Design.java
index 433705c632..db656937ef 100644
--- a/server/src/com/vaadin/ui/declarative/Design.java
+++ b/server/src/com/vaadin/ui/declarative/Design.java
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
+import java.lang.annotation.Annotation;
import java.util.Collection;
import org.jsoup.Jsoup;
@@ -32,6 +33,7 @@ import org.jsoup.nodes.Node;
import org.jsoup.parser.Parser;
import org.jsoup.select.Elements;
+import com.vaadin.annotations.DesignRoot;
import com.vaadin.ui.Component;
import com.vaadin.ui.declarative.DesignContext.ComponentCreatedEvent;
import com.vaadin.ui.declarative.DesignContext.ComponentCreationListener;
@@ -128,14 +130,43 @@ public class Design implements Serializable {
* @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 Component} are set when parsing the component tree
- *
+ * optional component root instance. The type must match the type
+ * of the root element in the design. Any member fields whose
+ * type is assignable from {@link Component} are bound to fields
+ * in the design based on id/local id/caption
*/
private static DesignContext designToComponentTree(Document doc,
Component componentRoot) {
+ if (componentRoot == null) {
+ return designToComponentTree(doc, null, null);
+ } else {
+ return designToComponentTree(doc, componentRoot,
+ componentRoot.getClass());
+ }
+
+ }
+
+ /**
+ * Constructs a component hierarchy from the design specified as an html
+ * tree.
+ *
+ * If a component root is given, the component instances created during
+ * synchronizing the design are assigned to its member fields based on their
+ * id, local id, and caption
+ *
+ * @param doc
+ * the html tree
+ * @param componentRoot
+ * optional component root instance. The type must match the type
+ * of the root element in the design.
+ * @param classWithFields
+ * a class (componentRoot class or a super class) with some
+ * member fields. The member fields whose type is assignable from
+ * {@link Component} are bound to fields in the design based on
+ * id/local id/caption
+ */
+ private static DesignContext designToComponentTree(Document doc,
+ Component componentRoot, Class<? extends Component> classWithFields) {
DesignContext designContext = new DesignContext(doc);
designContext.getPrefixes(doc);
// No special handling for a document without a body element - should be
@@ -151,20 +182,19 @@ public class Design implements Serializable {
if (componentRoot != null) {
// user has specified root instance that may have member fields that
// should be bound
- FieldBinder binder = null;
+ final FieldBinder binder;
try {
- binder = new FieldBinder(componentRoot);
+ binder = new FieldBinder(componentRoot, classWithFields);
} 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());
+ binder.bindField(event.getComponent(), event.getLocalId());
}
};
designContext.addComponentCreationListener(creationListener);
@@ -233,6 +263,94 @@ public class Design implements Serializable {
}
/**
+ * Loads a design for the given root component.
+ * <p>
+ * This methods assumes that the component class (or a super class) has been
+ * marked with an {@link DesignRoot} annotation and will either use the
+ * value from the annotation to locate the design file, or will fall back to
+ * using a design with the same same as the annotated class file (with an
+ * .html extension)
+ * <p>
+ * Any {@link Component} type fields in the root component which are not
+ * assigned (i.e. are null) are mapped to corresponding components in the
+ * design. Matching is done based on field name in the component class and
+ * id/local id/caption in the design file.
+ * <p>
+ * The type of the root component must match the root element in the design
+ *
+ * @param rootComponent
+ * The root component of the layout
+ * @return The design context used in the load operation
+ * @throws DesignException
+ * If the design could not be loaded
+ */
+ public static DesignContext read(Component rootComponent)
+ throws DesignException {
+ // Try to find an @DesignRoot annotation on the class or any parent
+ // class
+ Class<? extends Component> annotatedClass = findClassWithAnnotation(
+ rootComponent.getClass(), DesignRoot.class);
+ if (annotatedClass == null) {
+ throw new IllegalArgumentException(
+ "The class "
+ + rootComponent.getClass().getName()
+ + " or any of its superclasses do not have an @DesignRoot annotation");
+ }
+
+ DesignRoot designAnnotation = annotatedClass
+ .getAnnotation(DesignRoot.class);
+ String filename = designAnnotation.value();
+ if (filename.equals("")) {
+ // No value, assume the html file is named as the class
+ filename = annotatedClass.getSimpleName() + ".html";
+ }
+
+ InputStream stream = annotatedClass.getResourceAsStream(filename);
+ if (stream == null) {
+ throw new DesignException("Unable to find design file " + filename
+ + " in " + annotatedClass.getPackage().getName());
+ }
+
+ Document doc = parse(stream);
+ DesignContext context = designToComponentTree(doc, rootComponent,
+ annotatedClass);
+
+ return context;
+
+ }
+
+ /**
+ * Find the first class with the given annotation, starting the search from
+ * the given class and moving upwards in the class hierarchy.
+ *
+ * @param componentClass
+ * the class to check
+ * @param annotationClass
+ * the annotation to look for
+ * @return the first class with the given annotation or null if no class
+ * with the annotation was found
+ */
+ private static Class<? extends Component> findClassWithAnnotation(
+ Class<? extends Component> componentClass,
+ Class<? extends Annotation> annotationClass) {
+ if (componentClass == null) {
+ return null;
+ }
+
+ if (componentClass.isAnnotationPresent(annotationClass)) {
+ return componentClass;
+ }
+
+ Class<?> superClass = componentClass.getSuperclass();
+ if (!Component.class.isAssignableFrom(superClass)) {
+ return null;
+ }
+
+ return findClassWithAnnotation((Class<? extends Component>) superClass,
+ annotationClass);
+ }
+
+ /**
* Loads a design from the given file name using the given root component.
* <p>
* Any {@link Component} type fields in the root component which are not
@@ -246,7 +364,7 @@ public class Design implements Serializable {
* The file name to load. Loaded from the same package as the
* root component
* @param rootComponent
- * The root component of the layout.
+ * The root component of the layout
* @return The design context used in the load operation
* @throws DesignException
* If the design could not be loaded
@@ -276,7 +394,7 @@ public class Design implements Serializable {
* @param stream
* The stream to read the design from
* @param rootComponent
- * The root component of the layout.
+ * The root component of the layout
* @return The design context used in the load operation
* @throws DesignException
* If the design could not be loaded
diff --git a/server/src/com/vaadin/ui/declarative/DesignContext.java b/server/src/com/vaadin/ui/declarative/DesignContext.java
index a3026fca18..e84faa31ca 100644
--- a/server/src/com/vaadin/ui/declarative/DesignContext.java
+++ b/server/src/com/vaadin/ui/declarative/DesignContext.java
@@ -509,7 +509,7 @@ public class DesignContext implements Serializable {
Locale.ENGLISH) + tagName.substring(i + 2);
} else {
- // Ends with "-", WTF?
+ // Ends with "-"
System.out.println("A tag name should not end with '-'.");
}
}
diff --git a/server/src/com/vaadin/ui/declarative/FieldBinder.java b/server/src/com/vaadin/ui/declarative/FieldBinder.java
index f456b2e29a..4826c2be64 100644
--- a/server/src/com/vaadin/ui/declarative/FieldBinder.java
+++ b/server/src/com/vaadin/ui/declarative/FieldBinder.java
@@ -39,8 +39,8 @@ import com.vaadin.util.ReflectTools;
*/
public class FieldBinder implements Serializable {
- // the design class instance (the instance containing the bound fields)
- private Component bindTarget;
+ // the instance containing the bound fields
+ private Object bindTarget;
// mapping between field names and Fields
private Map<String, Field> fieldMap = new HashMap<String, Field>();
@@ -48,16 +48,31 @@ public class FieldBinder implements Serializable {
* Creates a new instance of LayoutFieldBinder
*
* @param design
- * the design class instance containing the bound fields
+ * the design class instance containing the fields to bind
* @throws IntrospectionException
* if the given design class can not be introspected
*/
- public FieldBinder(Component design) throws IntrospectionException {
+ public FieldBinder(Object design) throws IntrospectionException {
+ this(design, design.getClass());
+ }
+
+ /**
+ * Creates a new instance of LayoutFieldBinder
+ *
+ * @param design
+ * the instance containing the fields
+ * @param classWithFields
+ * the class which defines the fields to bind
+ * @throws IntrospectionException
+ * if the given design class can not be introspected
+ */
+ public FieldBinder(Object design, Class<?> classWithFields)
+ throws IntrospectionException {
if (design == null) {
throw new IllegalArgumentException("The design must not be null");
}
bindTarget = design;
- resolveFields();
+ resolveFields(classWithFields);
}
/**
@@ -91,8 +106,8 @@ public class FieldBinder implements Serializable {
/**
* Resolves the fields of the design class instance
*/
- private void resolveFields() {
- for (Field memberField : getFieldsInDeclareOrder(bindTarget.getClass())) {
+ private void resolveFields(Class<?> classWithFields) {
+ for (Field memberField : getFieldsInDeclareOrder(classWithFields)) {
if (Component.class.isAssignableFrom(memberField.getType())) {
fieldMap.put(memberField.getName().toLowerCase(Locale.ENGLISH),
memberField);