Skip to content

Commit

Permalink
Refactor model path handling (#931)
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur- authored and Legioth committed Jun 8, 2016
1 parent d45d709 commit b455491
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright 2000-2016 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.hummingbird.template.model;

import java.io.Serializable;
import java.util.regex.Pattern;

import com.vaadin.hummingbird.StateNode;
import com.vaadin.hummingbird.dom.impl.TemplateElementStateProvider;
import com.vaadin.hummingbird.nodefeature.ModelMap;
import com.vaadin.hummingbird.nodefeature.NodeFeature;

/**
* Resolver for finding the state node or model map for a given model path.
*/
public class ModelPathResolver {
private static final Pattern DOT_PATTERN = Pattern.compile("\\.");
private String[] modelPathParts;

/**
* Constructs a representation of the given model path.
*
* @param modelPath
* the path, in the format
* {@literal parent1.parent2.propertyName}.
*/
public ModelPathResolver(String modelPath) {
modelPathParts = DOT_PATTERN.split(modelPath);
}

/**
* Resolves the model path starting from the given stateNode. Returns the
* {@link ModelMap} which contains the property defined by the path, i.e.
* uses all parts up until the last "." in the path.
*
* @param stateNode
* the state node to start resolving from
* @return the model map of the resolved node
*/
public ModelMap resolveModelMap(StateNode stateNode) {
Class<ModelMap> childFeature = ModelMap.class;
StateNode node = stateNode;
// The last part is the propertyName
for (int i = 0; i < modelPathParts.length - 1; i++) {
node = resolveStateNode(node, modelPathParts[i], childFeature);
}
return node.getFeature(childFeature);
}

/**
* Gets the the property name, i.e. the last part of the path.
*
* @return the property name
*/
public String getPropertyName() {
return modelPathParts[modelPathParts.length - 1];
}

/**
* Finds a child node with the given name (one part of a model path) inside
* the given parent node.
* <p>
* Creates a new node with the given feature if no node is found or if a
* node is found but it does not have the correct feature.
*
* @param parentNode
* The parent state node
* @param childNodeName
* The name of the child node
* @param childFeature
* The feature to require from the child node
* @return a state node, old or new, with the required feature
*/
public static StateNode resolveStateNode(StateNode parentNode,
String childNodeName, Class<? extends NodeFeature> childFeature) {
assert !childNodeName.contains(".");

ModelMap parentLevel = parentNode.getFeature(ModelMap.class);
if (parentLevel.hasValue(childNodeName)) {
Serializable value = parentLevel.getValue(childNodeName);
if (value instanceof StateNode
&& ((StateNode) value).hasFeature(childFeature)) {
// reuse old one
return (StateNode) value;
} else {
// just override
return createSubModel(parentLevel, childNodeName, childFeature);
}
} else {
return createSubModel(parentLevel, childNodeName, childFeature);
}
}

/**
* Creates a sub model node with the given model feature and attaches it to
* the parent with the given name.
*
* @param parent
* the parent model map
* @param propertyName
* the name to use when attaching
* @param childFeature
* the feature (ModelMap or ModelList) to use
* @return a new state node with the given feature
*/
private static StateNode createSubModel(ModelMap parent,
String propertyName, Class<? extends NodeFeature> childFeature) {
StateNode node = TemplateElementStateProvider
.createSubModelNode(childFeature);
parent.setValue(propertyName, node);
return node;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -75,36 +75,36 @@ default void importBean(Object bean) {
* path "person" represents {@code getPerson()} return value and the path
* "person.address" represents {@code getPerson().getAddress()} return
* value.
*
*
* <pre>
* <code>
* public class Address {
* private String street;
* private String street;
* public String getStreet(){
* return street;
* }
*
*
* public void setStreet(String street){
* this.street = street;
* }
* }
*
*
* public class Person {
* private String name;
* private Address address;
*
*
* public String getName(){
* return name;
* }
*
*
* public void setName(String name){
* this.name = name;
* }
*
*
* public void setAddress(Address address){
* this.address = address;
* }
*
*
* public Address getAddress(){
* return address;
* }
Expand All @@ -114,7 +114,7 @@ default void importBean(Object bean) {
* }
* </code>
* </pre>
*
*
* @param modelPath
* dot separated path denoting subproperty bean
* @param beanType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@
import java.util.Objects;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.vaadin.hummingbird.StateNode;
import com.vaadin.hummingbird.dom.impl.TemplateElementStateProvider;
import com.vaadin.hummingbird.nodefeature.ModelList;
import com.vaadin.hummingbird.nodefeature.ModelMap;
import com.vaadin.hummingbird.nodefeature.NodeFeature;
import com.vaadin.util.ReflectTools;

/**
Expand All @@ -46,8 +44,6 @@ public class TemplateModelBeanUtil {
private static final Class<?>[] SUPPORTED_PROPERTY_TYPES = new Class[] {
Boolean.class, Double.class, Integer.class, String.class };

private static final Pattern DOT_PATTERN = Pattern.compile("\\.");

/**
* Internal implementation of Pair / Tuple that encapsulates a value and the
* method that it was retrieved from.
Expand Down Expand Up @@ -148,18 +144,18 @@ static Object getProxy(StateNode stateNode, Object[] args) {
Class<?> beanClass = (Class<?>) args[1];

if (modelPath.isEmpty()) {
// get the whole model as a bean
return TemplateModelProxyHandler.createModelProxy(stateNode,
beanClass);
}

StateNode node = stateNode;
ModelPathResolver resolver = new ModelPathResolver(modelPath);
ModelMap parentMap = resolver.resolveModelMap(stateNode);
// Create the state node for the bean if it does not exist
ModelPathResolver.resolveStateNode(parentMap.getNode(),
resolver.getPropertyName(), ModelMap.class);

String[] path = DOT_PATTERN.split(modelPath);
for (int i = 0; i < path.length; i++) {
node = resolveStateNode(node, path[i], ModelMap.class);
}
return getModelValue(node.getParent().getFeature(ModelMap.class),
path[path.length - 1], beanClass);
return getModelValue(parentMap, resolver.getPropertyName(), beanClass);
}

private static void setModelValueBasicType(ModelMap modelMap,
Expand All @@ -183,8 +179,8 @@ private static void setModelValueBasicType(ModelMap modelMap,
// handle other types as beans
String newPathPrefix = pathPrefix + propertyName + ".";
importBeanIntoModel(
() -> resolveStateNode(modelMap.getNode(), propertyName,
ModelMap.class),
() -> ModelPathResolver.resolveStateNode(modelMap.getNode(),
propertyName, ModelMap.class),
expectedType, value, newPathPrefix, filter);
return;
}
Expand Down Expand Up @@ -226,8 +222,9 @@ private static void importListIntoModel(StateNode parentNode, List<?> list,
childNodes.add(childNode);
}

ModelList modelList = resolveStateNode(parentNode, propertyName,
ModelList.class).getFeature(ModelList.class);
ModelList modelList = ModelPathResolver
.resolveStateNode(parentNode, propertyName, ModelList.class)
.getFeature(ModelList.class);
modelList.clear();

modelList.addAll(childNodes);
Expand Down Expand Up @@ -284,32 +281,6 @@ private static ModelPropertyWrapper mapBeanValueToProperty(
}
}

private static StateNode resolveStateNode(StateNode parentNode,
String childNodePath, Class<? extends NodeFeature> childFeature) {
ModelMap parentLevel = parentNode.getFeature(ModelMap.class);
if (parentLevel.hasValue(childNodePath)) {
Serializable value = parentLevel.getValue(childNodePath);
if (value instanceof StateNode
&& ((StateNode) value).hasFeature(childFeature)) {
// reuse old one
return (StateNode) value;
} else {
// just override
return createSubModel(parentLevel, childNodePath, childFeature);
}
} else {
return createSubModel(parentLevel, childNodePath, childFeature);
}
}

private static StateNode createSubModel(ModelMap parent,
String propertyName, Class<? extends NodeFeature> childFeature) {
StateNode node = TemplateElementStateProvider
.createSubModelNode(childFeature);
parent.setValue(propertyName, node);
return node;
}

private static String getSupportedTypesString() {
return Stream.of(SUPPORTED_PROPERTY_TYPES).map(Class::getName)
.collect(Collectors.joining(", "))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2000-2016 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.hummingbird.template.model;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.vaadin.hummingbird.StateNode;
import com.vaadin.hummingbird.dom.impl.TemplateElementStateProvider;
import com.vaadin.hummingbird.nodefeature.ModelMap;

public class ModelPathResolverTest {

StateNode root;

@Before
public void setup() {
root = TemplateElementStateProvider.createRootNode();
}

@Test
public void resolveEmptyPath() {
ModelPathResolver resolver = new ModelPathResolver("");
ModelMap map = resolver.resolveModelMap(root);
Assert.assertEquals(root.getFeature(ModelMap.class), map);
}

@Test
public void resolveProperty() {
ModelPathResolver resolver = new ModelPathResolver("foo");
ModelMap map = resolver.resolveModelMap(root);
Assert.assertEquals(root.getFeature(ModelMap.class), map);
}

@Test
public void resolveSubProperty() {
ModelMap rootMap = root.getFeature(ModelMap.class);
Assert.assertFalse(rootMap.hasValue("foo"));

ModelMap map = new ModelPathResolver("foo.bar").resolveModelMap(root);
Assert.assertTrue(rootMap.hasValue("foo"));
ModelMap fooMap = ((StateNode) rootMap.getValue("foo"))
.getFeature(ModelMap.class);
Assert.assertEquals(fooMap, map);
}

@Test
public void resolveSubSubProperty() {
ModelMap map = new ModelPathResolver("foo.bar.baz")
.resolveModelMap(root);
ModelMap rootMap = root.getFeature(ModelMap.class);
ModelMap fooMap = ((StateNode) rootMap.getValue("foo"))
.getFeature(ModelMap.class);
ModelMap barMap = ((StateNode) fooMap.getValue("bar"))
.getFeature(ModelMap.class);

Assert.assertEquals(barMap, map);
}

@Test
public void propertyNameForEmpty() {
ModelPathResolver resolver = new ModelPathResolver("");
Assert.assertEquals("", resolver.getPropertyName());
}

@Test
public void propertyNameForProperty() {
ModelPathResolver resolver = new ModelPathResolver("foo");
Assert.assertEquals("foo", resolver.getPropertyName());
}

@Test
public void propertyNameForSubProperty() {
ModelPathResolver resolver = new ModelPathResolver("foo.bar");
Assert.assertEquals("bar", resolver.getPropertyName());
}

@Test
public void propertyNameForSubSubProperty() {
ModelPathResolver resolver = new ModelPathResolver("foo.bar.baz");
Assert.assertEquals("baz", resolver.getPropertyName());
}

}

0 comments on commit b455491

Please sign in to comment.