Ver código fonte

Add public API for loading design based on @DesignRoot (#7749)

Change-Id: Ic6f201a45d66aefe9ec93ba3be5a75b6532bf014
tags/7.4.0.beta1
Artur Signell 9 anos atrás
pai
commit
898e28d6ae

+ 41
- 0
server/src/com/vaadin/annotations/DesignRoot.java Ver arquivo

@@ -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 "";
}

+ 129
- 11
server/src/com/vaadin/ui/declarative/Design.java Ver arquivo

@@ -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);
@@ -232,6 +262,94 @@ public class Design implements Serializable {
writer.write(docAsString);
}

/**
* 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>
@@ -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

+ 1
- 1
server/src/com/vaadin/ui/declarative/DesignContext.java Ver arquivo

@@ -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 '-'.");
}
}

+ 22
- 7
server/src/com/vaadin/ui/declarative/FieldBinder.java Ver arquivo

@@ -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);

+ 53
- 0
server/tests/src/com/vaadin/tests/design/designroot/DesignRootTest.java Ver arquivo

@@ -0,0 +1,53 @@
/*
* 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.design.designroot;

import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;

// This test will not pass until default instance creation is changed. Will leave it ignored for now.
@Ignore
public class DesignRootTest {
@Test
public void designAnnotationWithoutFilename() {
DesignWithEmptyAnnotation d = new DesignWithEmptyAnnotation();
Assert.assertNotNull(d.ok);
Assert.assertNotNull(d.CaNCEL);
}

@Test
public void designAnnotationWithFilename() {
DesignWithAnnotation d = new DesignWithAnnotation();
Assert.assertNotNull(d.ok);
Assert.assertNotNull(d.cancel);
}

@Test
public void extendedDesignAnnotationWithoutFilename() {
DesignWithEmptyAnnotation d = new ExtendedDesignWithEmptyAnnotation();
Assert.assertNotNull(d.ok);
Assert.assertNotNull(d.CaNCEL);
}

@Test
public void extendedDesignAnnotationWithFilename() {
DesignWithAnnotation d = new ExtendedDesignWithAnnotation();
Assert.assertNotNull(d.ok);
Assert.assertNotNull(d.cancel);
}

}

+ 35
- 0
server/tests/src/com/vaadin/tests/design/designroot/DesignWithAnnotation.java Ver arquivo

@@ -0,0 +1,35 @@
/*
* 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.design.designroot;

import org.junit.Ignore;

import com.vaadin.annotations.DesignRoot;
import com.vaadin.ui.Button;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.declarative.Design;

@DesignRoot("DesignWithEmptyAnnotation.html")
@Ignore
public class DesignWithAnnotation extends VerticalLayout {

public Button ok;
public Button cancel;

public DesignWithAnnotation() {
Design.read(this);
}
}

+ 4
- 0
server/tests/src/com/vaadin/tests/design/designroot/DesignWithEmptyAnnotation.html Ver arquivo

@@ -0,0 +1,4 @@
<v-vertical-layout>
<v-button>OK</v-button>
<v-button>Cancel</v-button>
</v-vertical-layout>

+ 35
- 0
server/tests/src/com/vaadin/tests/design/designroot/DesignWithEmptyAnnotation.java Ver arquivo

@@ -0,0 +1,35 @@
/*
* 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.design.designroot;

import org.junit.Ignore;

import com.vaadin.annotations.DesignRoot;
import com.vaadin.ui.Button;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.declarative.Design;

@DesignRoot
@Ignore
public class DesignWithEmptyAnnotation extends VerticalLayout {

protected Button ok;
protected Button CaNCEL;

public DesignWithEmptyAnnotation() {
Design.read(this);
}
}

+ 31
- 0
server/tests/src/com/vaadin/tests/design/designroot/ExtendedDesignWithAnnotation.java Ver arquivo

@@ -0,0 +1,31 @@
/*
* 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.design.designroot;

import org.junit.Ignore;

import com.vaadin.ui.TextField;

@Ignore
public class ExtendedDesignWithAnnotation extends DesignWithAnnotation {
private TextField customField = new TextField();

public ExtendedDesignWithAnnotation() {
customField.setInputPrompt("Something");
addComponent(customField);

}
}

+ 50
- 0
server/tests/src/com/vaadin/tests/design/designroot/ExtendedDesignWithEmptyAnnotation.java Ver arquivo

@@ -0,0 +1,50 @@
/*
* 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.design.designroot;

import org.junit.Ignore;

import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Notification;
import com.vaadin.ui.TextField;

@Ignore
public class ExtendedDesignWithEmptyAnnotation extends
DesignWithEmptyAnnotation {

private TextField customField = new TextField();

public ExtendedDesignWithEmptyAnnotation() {
super();
customField.setInputPrompt("Something");
addComponent(customField);

ok.addClickListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
Notification.show("OK");
}
});

CaNCEL.addClickListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
Notification.show("cancel");
}
});
}
}

+ 32
- 0
server/tests/src/com/vaadin/tests/design/designroot/ExtendedDesignWithEmptyAnnotationUI.java Ver arquivo

@@ -0,0 +1,32 @@
/*
* 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.design.designroot;

import org.junit.Ignore;

import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.UI;

@Ignore
public class ExtendedDesignWithEmptyAnnotationUI extends UI {

@Override
protected void init(VaadinRequest request) {
setContent(new ExtendedDesignWithEmptyAnnotation());

}

}

Carregando…
Cancelar
Salvar