blob: 6213ddc193522df0a63646d0cd81332e9d016986 [file] [log] [blame]
// Copyright 2007, 2008, 2010 The Apache Software Foundation
//
// 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 org.apache.tapestry5.internal.beaneditor;
import java.util.List;
import java.util.Map;
import org.apache.tapestry5.PropertyConduit;
import org.apache.tapestry5.beaneditor.BeanModel;
import org.apache.tapestry5.beaneditor.PropertyModel;
import org.apache.tapestry5.beaneditor.RelativePosition;
import org.apache.tapestry5.internal.services.CoercingPropertyConduitWrapper;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.ObjectLocator;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.services.ClassFabUtils;
import org.apache.tapestry5.ioc.services.TypeCoercer;
import org.apache.tapestry5.ioc.util.AvailableValues;
import org.apache.tapestry5.ioc.util.UnknownValueException;
import org.apache.tapestry5.services.PropertyConduitSource;
public class BeanModelImpl<T> implements BeanModel<T>
{
private final Class<T> beanType;
private final PropertyConduitSource propertyConduitSource;
private final TypeCoercer typeCoercer;
private final Messages messages;
private final ObjectLocator locator;
private final Map<String, PropertyModel> properties = CollectionFactory.newCaseInsensitiveMap();
// The list of property names, in desired order (generally not alphabetical order).
private final List<String> propertyNames = CollectionFactory.newList();
public BeanModelImpl(Class<T> beanType, PropertyConduitSource propertyConduitSource, TypeCoercer typeCoercer,
Messages messages, ObjectLocator locator)
{
this.beanType = beanType;
this.propertyConduitSource = propertyConduitSource;
this.typeCoercer = typeCoercer;
this.messages = messages;
this.locator = locator;
}
public Class<T> getBeanType()
{
return beanType;
}
public T newInstance()
{
return locator.autobuild("Instantiating new instance of " + beanType.getName(), beanType);
}
public PropertyModel add(String propertyName)
{
PropertyConduit conduit = createConduit(propertyName);
return add(propertyName, conduit);
}
private void validateNewPropertyName(String propertyName)
{
assert InternalUtils.isNonBlank(propertyName);
if (properties.containsKey(propertyName))
throw new RuntimeException(String.format(
"Bean editor model for %s already contains a property model for property '%s'.",
beanType.getName(), propertyName));
}
public PropertyModel add(RelativePosition position, String existingPropertyName, String propertyName,
PropertyConduit conduit)
{
assert position != null;
validateNewPropertyName(propertyName);
// Locate the existing one.
PropertyModel existing = get(existingPropertyName);
// Use the case normalized property name.
int pos = propertyNames.indexOf(existing.getPropertyName());
PropertyModel newModel = new PropertyModelImpl(this, propertyName, conduit, messages);
properties.put(propertyName, newModel);
int offset = position == RelativePosition.AFTER ? 1 : 0;
propertyNames.add(pos + offset, propertyName);
return newModel;
}
public PropertyModel add(RelativePosition position, String existingPropertyName, String propertyName)
{
PropertyConduit conduit = createConduit(propertyName);
return add(position, existingPropertyName, propertyName, conduit);
}
public PropertyModel add(String propertyName, PropertyConduit conduit)
{
validateNewPropertyName(propertyName);
PropertyModel propertyModel = new PropertyModelImpl(this, propertyName, conduit, messages);
properties.put(propertyName, propertyModel);
// Remember the order in which the properties were added.
propertyNames.add(propertyName);
return propertyModel;
}
private CoercingPropertyConduitWrapper createConduit(String propertyName)
{
return new CoercingPropertyConduitWrapper(propertyConduitSource.create(beanType, propertyName), typeCoercer);
}
public PropertyModel get(String propertyName)
{
PropertyModel propertyModel = properties.get(propertyName);
if (propertyModel == null)
throw new UnknownValueException(String.format(
"Bean editor model for %s does not contain a property named '%s'.", beanType.getName(),
propertyName), new AvailableValues("Defined properties", propertyNames));
return propertyModel;
}
public PropertyModel getById(String propertyId)
{
for (PropertyModel model : properties.values())
{
if (model.getId().equalsIgnoreCase(propertyId))
return model;
}
// Not found, so we throw an exception. A bit of work to set
// up the exception however.
List<String> ids = CollectionFactory.newList();
for (PropertyModel model : properties.values())
{
ids.add(model.getId());
}
throw new UnknownValueException(String.format(
"Bean editor model for %s does not contain a property with id '%s'.", beanType.getName(), propertyId),
new AvailableValues("Defined property ids", ids));
}
public List<String> getPropertyNames()
{
return CollectionFactory.newList(propertyNames);
}
public BeanModel<T> exclude(String... propertyNames)
{
for (String propertyName : propertyNames)
{
PropertyModel model = properties.get(propertyName);
if (model == null)
continue;
// De-referencing from the model is needed because the name provided may not be a
// case-exact match, so we get the normalized or canonical name from the model because
// that's the one in propertyNames.
this.propertyNames.remove(model.getPropertyName());
properties.remove(propertyName);
}
return this;
}
public BeanModel<T> reorder(String... propertyNames)
{
List<String> remainingPropertyNames = CollectionFactory.newList(this.propertyNames);
List<String> reorderedPropertyNames = CollectionFactory.newList();
for (String name : propertyNames)
{
PropertyModel model = get(name);
// Get the canonical form (which may differ from name in terms of case)
String canonical = model.getPropertyName();
reorderedPropertyNames.add(canonical);
remainingPropertyNames.remove(canonical);
}
this.propertyNames.clear();
this.propertyNames.addAll(reorderedPropertyNames);
// Any unspecified names are ordered to the end. Don't want them? Remove them instead.
this.propertyNames.addAll(remainingPropertyNames);
return this;
}
public BeanModel<T> include(String... propertyNames)
{
List<String> reorderedPropertyNames = CollectionFactory.newList();
Map<String, PropertyModel> reduced = CollectionFactory.newCaseInsensitiveMap();
for (String name : propertyNames)
{
PropertyModel model = get(name);
String canonical = model.getPropertyName();
reorderedPropertyNames.add(canonical);
reduced.put(canonical, model);
}
this.propertyNames.clear();
this.propertyNames.addAll(reorderedPropertyNames);
properties.clear();
properties.putAll(reduced);
return this;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder("BeanModel[");
builder.append(ClassFabUtils.toJavaClassName(beanType));
builder.append(" properties:");
String sep = "";
for (String name : propertyNames)
{
builder.append(sep);
builder.append(name);
sep = ", ";
}
builder.append("]");
return builder.toString();
}
}