/*
* ModeShape (http://www.modeshape.org)
*
* 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.modeshape.jcr.value;
import java.math.BigDecimal;
import java.net.URI;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.modeshape.common.util.Base64;
import org.modeshape.jcr.api.value.DateTime;
import org.modeshape.jcr.cache.NodeKey;
import org.modeshape.jcr.query.model.TypeSystem;
import org.modeshape.jcr.value.basic.NodeKeyReference;
/**
*
*/
public final class ValueTypeSystem extends TypeSystem {
private final String defaultTypeName;
protected final ValueFactories valueFactories;
protected final ValueFactory<String> stringValueFactory;
private final Map<PropertyType, TypeFactory<?>> typeFactoriesByPropertyType;
private final Map<String, TypeFactory<?>> typeFactoriesByName;
private final Map<String, PropertyType> propertyTypeByName;
private final TypeFactory<String> stringFactory;
private final TypeFactory<Boolean> booleanFactory;
private final TypeFactory<Long> longFactory;
private final TypeFactory<Double> doubleFactory;
private final TypeFactory<BigDecimal> decimalFactory;
private final TypeFactory<DateTime> dateFactory;
private final TypeFactory<Path> pathFactory;
private final TypeFactory<Name> nameFactory;
private final TypeFactory<Reference> referenceFactory;
private final TypeFactory<BinaryValue> binaryFactory;
private final TypeFactory<NodeKey> nodeKeyFactory;
/**
* Create a type system using the supplied value factories.
*
* @param valueFactories the value factories;
* @throws IllegalArgumentException if the value factories are null
*/
public ValueTypeSystem( ValueFactories valueFactories ) {
this(valueFactories, null);
}
/**
* Create a type system using the supplied value factories and locale
*
* @param valueFactories the value factories;
* @param locale the locale, may not be null
* @throws IllegalArgumentException if the value factories are null
*/
public ValueTypeSystem( final ValueFactories valueFactories, final Locale locale ) {
this.valueFactories = valueFactories;
this.defaultTypeName = PropertyType.STRING.getName().toUpperCase();
Map<PropertyType, TypeFactory<?>> factories = new HashMap<>();
this.stringValueFactory = valueFactories.getStringFactory();
this.stringFactory = new Factory<String>(stringValueFactory) {
@Override
public String asString( Object value ) {
return stringValueFactory.create(value);
}
@Override
public String asReadableString( Object value ) {
return stringValueFactory.create(value);
}
@Override
@SuppressWarnings( { "unchecked", "rawtypes" } )
public Comparator<String> getComparator() {
return locale == null ? super.getComparator() : ValueComparators.collatorComparator(locale);
}
};
this.booleanFactory = new Factory<Boolean>(valueFactories.getBooleanFactory());
this.longFactory = new Factory<Long>(valueFactories.getLongFactory());
this.doubleFactory = new Factory<Double>(valueFactories.getDoubleFactory());
this.decimalFactory = new Factory<BigDecimal>(valueFactories.getDecimalFactory());
this.dateFactory = new Factory<DateTime>(valueFactories.getDateFactory()) {
@Override
public DateTime create( String value ) throws ValueFormatException {
DateTime result = valueFactory.create(value);
// Convert the timestamp to UTC, since that's how everything should be queried ...
return result.toUtcTimeZone();
}
};
this.pathFactory = new Factory<Path>(valueFactories.getPathFactory());
this.nameFactory = new Factory<Name>(valueFactories.getNameFactory());
this.referenceFactory = new Factory<Reference>(valueFactories.getReferenceFactory());
this.nodeKeyFactory = new NodeKeyTypeFactory(stringValueFactory);
this.binaryFactory = new Factory<BinaryValue>(valueFactories.getBinaryFactory()) {
@Override
public String asReadableString( Object value ) {
BinaryValue binary = this.valueFactory.create(value);
// Just print out the SHA-1 hash in Base64, plus length
return "(Binary,length=" + binary.getSize() + ",SHA1=" + Base64.encodeBytes(binary.getHash()) + ")";
}
@Override
public long length( Object value ) {
BinaryValue binary = this.valueFactory.create(value);
return binary != null ? binary.getSize() : 0;
}
};
factories.put(PropertyType.STRING, this.stringFactory);
factories.put(PropertyType.BOOLEAN, this.booleanFactory);
factories.put(PropertyType.DATE, this.dateFactory);
factories.put(PropertyType.DECIMAL, new Factory<BigDecimal>(valueFactories.getDecimalFactory()));
factories.put(PropertyType.DOUBLE, this.doubleFactory);
factories.put(PropertyType.LONG, this.longFactory);
factories.put(PropertyType.NAME, new Factory<Name>(valueFactories.getNameFactory()));
factories.put(PropertyType.OBJECT, new Factory<Object>(valueFactories.getObjectFactory()));
factories.put(PropertyType.PATH, this.pathFactory);
factories.put(PropertyType.REFERENCE, new Factory<Reference>(valueFactories.getReferenceFactory()));
factories.put(PropertyType.WEAKREFERENCE, new Factory<Reference>(valueFactories.getWeakReferenceFactory()));
factories.put(PropertyType.SIMPLEREFERENCE, new Factory<Reference>(valueFactories.getSimpleReferenceFactory()));
factories.put(PropertyType.URI, new Factory<URI>(valueFactories.getUriFactory()));
factories.put(PropertyType.BINARY, this.binaryFactory);
this.typeFactoriesByPropertyType = Collections.unmodifiableMap(factories);
Map<String, PropertyType> propertyTypeByName = new HashMap<String, PropertyType>();
for (Map.Entry<PropertyType, TypeFactory<?>> entry : this.typeFactoriesByPropertyType.entrySet()) {
propertyTypeByName.put(entry.getValue().getTypeName(), entry.getKey());
}
this.propertyTypeByName = Collections.unmodifiableMap(propertyTypeByName);
Map<String, TypeFactory<?>> byName = new HashMap<>();
for (TypeFactory<?> factory : factories.values()) {
byName.put(factory.getTypeName(), factory);
}
byName.put(nodeKeyFactory.getTypeName(), nodeKeyFactory);
this.typeFactoriesByName = Collections.unmodifiableMap(byName);
}
@Override
public String asString( Object value ) {
return stringValueFactory.create(value);
}
@Override
public TypeFactory<Boolean> getBooleanFactory() {
return booleanFactory;
}
@Override
public TypeFactory<String> getStringFactory() {
return this.stringFactory;
}
@Override
public TypeFactory<?> getDateTimeFactory() {
return dateFactory;
}
@Override
public String getDefaultType() {
return defaultTypeName;
}
@Override
@SuppressWarnings( "unchecked" )
public Comparator<Object> getDefaultComparator() {
return (Comparator<Object>)PropertyType.OBJECT.getComparator();
}
@Override
public TypeFactory<Double> getDoubleFactory() {
return doubleFactory;
}
@Override
public TypeFactory<BigDecimal> getDecimalFactory() {
return decimalFactory;
}
@Override
public TypeFactory<Long> getLongFactory() {
return longFactory;
}
@Override
public TypeFactory<Path> getPathFactory() {
return pathFactory;
}
@Override
public TypeFactory<Name> getNameFactory() {
return nameFactory;
}
@Override
public TypeFactory<Reference> getReferenceFactory() {
return referenceFactory;
}
@Override
public TypeFactory<BinaryValue> getBinaryFactory() {
return binaryFactory;
}
@Override
public TypeFactory<NodeKey> getNodeKeyFactory() {
return nodeKeyFactory;
}
@Override
public TypeFactory<?> getTypeFactory( String typeName ) {
if (typeName == null) return null;
return typeFactoriesByName.get(typeName.toUpperCase()); // may be null
}
@Override
public TypeFactory<?> getTypeFactory( Object prototype ) {
ValueFactory<?> valueFactory = valueFactories.getValueFactory(prototype);
if (valueFactory == null) return null;
PropertyType type = valueFactory.getPropertyType();
assert type != null;
return typeFactoriesByPropertyType.get(type);
}
@Override
public Set<String> getTypeNames() {
return typeFactoriesByName.keySet();
}
@Override
public String getCompatibleType( String type1,
String type2 ) {
if (type1 == null) {
return type2 != null ? type2 : getDefaultType();
}
if (type2 == null) return type1;
if (type1.equals(type2)) return type1;
// neither is null ...
PropertyType ptype1 = propertyTypeByName.get(type1);
PropertyType ptype2 = propertyTypeByName.get(type2);
assert ptype1 != null;
assert ptype2 != null;
if (ptype1 == PropertyType.STRING) return type1;
if (ptype2 == PropertyType.STRING) return type2;
// Dates are compatible with longs ...
if (ptype1 == PropertyType.LONG && ptype2 == PropertyType.DATE) return type1;
if (ptype1 == PropertyType.DATE && ptype2 == PropertyType.LONG) return type2;
// Booleans and longs are compatible ...
if (ptype1 == PropertyType.LONG && ptype2 == PropertyType.BOOLEAN) return type1;
if (ptype1 == PropertyType.BOOLEAN && ptype2 == PropertyType.LONG) return type2;
// Doubles and longs ...
if (ptype1 == PropertyType.DOUBLE && ptype2 == PropertyType.LONG) return type1;
if (ptype1 == PropertyType.LONG && ptype2 == PropertyType.DOUBLE) return type2;
// Paths and names ...
if (ptype1 == PropertyType.PATH && ptype2 == PropertyType.NAME) return type1;
if (ptype1 == PropertyType.NAME && ptype2 == PropertyType.PATH) return type2;
// Otherwise, it's just the default type (string) ...
return getDefaultType();
}
@Override
public TypeFactory<?> getCompatibleType( TypeFactory<?> type1,
TypeFactory<?> type2 ) {
return getTypeFactory(getCompatibleType(type1.getTypeName(), type2.getTypeName()));
}
protected class Factory<T> implements TypeFactory<T> {
protected final PropertyType type;
protected final ValueFactory<T> valueFactory;
protected final String typeName;
protected Factory( ValueFactory<T> valueFactory ) {
this.valueFactory = valueFactory;
this.type = this.valueFactory.getPropertyType();
this.typeName = type.getName().toUpperCase();
}
@Override
public String asReadableString( Object value ) {
return asString(value);
}
@Override
public String asString( Object value ) {
if (value instanceof String) {
// Convert to the typed value, then back to a string ...
value = valueFactory.create((String)value);
}
return stringValueFactory.create(value);
}
@Override
public T create( String value ) throws ValueFormatException {
return valueFactory.create(value);
}
@Override
public T create( Object value ) throws ValueFormatException {
return valueFactory.create(value);
}
@Override
@SuppressWarnings( "unchecked" )
public Class<T> getType() {
return (Class<T>)type.getValueClass();
}
@Override
public long length( Object value ) {
String str = asString(valueFactory.create(value));
return str != null ? str.length() : 0;
}
@Override
@SuppressWarnings( "unchecked" )
public Comparator<T> getComparator() {
return (Comparator<T>)type.getComparator();
}
@Override
public String getTypeName() {
return typeName;
}
@Override
public String toString() {
return "TypeFactory<" + getTypeName() + ">";
}
}
protected static class NodeKeyTypeFactory implements TypeFactory<NodeKey> {
private final ValueFactory<String> stringFactory;
protected NodeKeyTypeFactory( ValueFactory<String> stringFactory ) {
this.stringFactory = stringFactory;
}
@Override
public Class<NodeKey> getType() {
return NodeKey.class;
}
@Override
public String getTypeName() {
return getType().getName().toUpperCase();
}
@Override
public String asString( Object value ) {
return ((NodeKey)value).toString();
}
@Override
public String asReadableString( Object value ) {
return asString(value);
}
@Override
public long length( Object value ) {
return asString(value).length();
}
@Override
public NodeKey create( Object value ) throws ValueFormatException {
if (value == null) return null;
if (value instanceof NodeKey) {
return (NodeKey)value;
}
if (value instanceof NodeKeyReference) {
return ((NodeKeyReference)value).getNodeKey();
}
String str = stringFactory.create(value);
return create(str);
}
@Override
public NodeKey create( String value ) throws ValueFormatException {
if (NodeKey.isValidFormat(value)) {
return new NodeKey(value);
}
throw new ValueFormatException(value, PropertyType.OBJECT, "Unable to convert " + value.getClass() + " to a NodeKey");
}
@Override
public Comparator<NodeKey> getComparator() {
return NodeKey.COMPARATOR;
}
}
}