/* * 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; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.net.URI; import java.util.Calendar; import javax.jcr.Node; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import org.modeshape.common.SystemFailureException; import org.modeshape.common.annotation.NotThreadSafe; import org.modeshape.common.util.IoUtil; import org.modeshape.jcr.api.value.DateTime; import org.modeshape.jcr.value.BinaryFactory; import org.modeshape.jcr.value.BinaryValue; import org.modeshape.jcr.value.DateTimeFactory; import org.modeshape.jcr.value.Name; import org.modeshape.jcr.value.NameFactory; import org.modeshape.jcr.value.Path; import org.modeshape.jcr.value.PathFactory; import org.modeshape.jcr.value.ValueFactories; /** * ModeShape implementation of a {@link Value JCR Value}. */ @NotThreadSafe final class JcrValue implements javax.jcr.Value { private final ValueFactories factories; private final int type; private final Object value; private InputStream asStream = null; JcrValue( ValueFactories factories, int type, Object value ) { assert factories != null; this.factories = factories; assert type == PropertyType.BINARY || type == PropertyType.BOOLEAN || type == PropertyType.DATE || type == PropertyType.DECIMAL || type == PropertyType.DOUBLE || type == PropertyType.LONG || type == PropertyType.NAME || type == PropertyType.PATH || type == PropertyType.REFERENCE || type == PropertyType.WEAKREFERENCE || type == PropertyType.STRING || type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE || type == PropertyType.URI : "Unxpected PropertyType: " + org.modeshape.jcr.api.PropertyType.nameFromValue(type) + " for value " + (value == null ? "null" : ("\"" + value + "\"")); // Leaving this assertion out for now so that values can be created in node type sources, which are created outside // the context of any particular session. // assert sessionCache != null; assert value != null; this.type = type; this.value = convertToType(this.type, value); } JcrValue( ValueFactories factories, Value value ) throws RepositoryException { assert factories != null; assert value != null; this.factories = factories; this.type = value.getType(); this.value = valueToType(this.type, value); } private ValueFormatException createValueFormatException( Class<?> type ) { return new ValueFormatException(JcrI18n.cannotConvertValue.text(value.getClass().getSimpleName(), type.getSimpleName())); } private ValueFormatException createValueFormatException( org.modeshape.jcr.value.ValueFormatException vfe ) { return new ValueFormatException(vfe); } final ValueFactories factories() { return factories; } /** * Returns a direct reference to the internal value object wrapped by this {@link JcrValue}. Useful to avoid the expense of * {@link #asType(int)} if the caller already knows the type of the value. * * @return a reference to the {@link #value} field. */ final Object value() { return value; } @Override public boolean getBoolean() throws ValueFormatException { try { boolean convertedValue = factories().getBooleanFactory().create(value); return convertedValue; } catch (RuntimeException error) { throw createValueFormatException(boolean.class); } } @Override public Calendar getDate() throws ValueFormatException { if (value == null) return null; try { Calendar convertedValue = factories().getDateFactory().create(value).toCalendar(); return convertedValue; } catch (RuntimeException error) { throw createValueFormatException(Calendar.class); } } @Override public BigDecimal getDecimal() throws ValueFormatException, RepositoryException { if (value == null) return null; try { BigDecimal convertedValue = factories().getDecimalFactory().create(value); return convertedValue; } catch (RuntimeException error) { throw createValueFormatException(double.class); } } @Override public double getDouble() throws ValueFormatException { try { double convertedValue = factories().getDoubleFactory().create(value); return convertedValue; } catch (RuntimeException error) { throw createValueFormatException(double.class); } } long getLength() throws RepositoryException { if (value == null) return 0L; if (type == PropertyType.BINARY) { return factories().getBinaryFactory().create(value).getSize(); } return getString().length(); } @Override public long getLong() throws ValueFormatException { try { long convertedValue = factories().getLongFactory().create(value); return convertedValue; } catch (RuntimeException error) { throw createValueFormatException(long.class); } } @Override @SuppressWarnings("deprecation") public InputStream getStream() throws ValueFormatException { if (value == null) return null; try { if (asStream == null) { BinaryValue binary = factories().getBinaryFactory().create(value); asStream = binary.getStream(); } return asStream; } catch (Exception error) { throw createValueFormatException(InputStream.class); } } @Override public BinaryValue getBinary() throws RepositoryException { if (value == null) return null; try { BinaryValue binary = factories().getBinaryFactory().create(value); return binary; } catch (RuntimeException error) { throw createValueFormatException(InputStream.class); } } @Override public String getString() throws ValueFormatException { try { return factories().getStringFactory().create(value); } catch (RuntimeException error) { throw createValueFormatException(String.class); } } @Override public int getType() { return type; } @Override public int hashCode() { // Use the value's hash code return value.hashCode(); } @Override public boolean equals( Object obj ) { if (obj == this) return true; if (obj instanceof JcrValue) { JcrValue that = (JcrValue)obj; if (this.type != that.type) return false; try { switch (this.type) { case PropertyType.STRING: return this.getString().equals(that.getString()); case PropertyType.BINARY: BinaryFactory binaryFactory = factories().getBinaryFactory(); BinaryValue thisValue = binaryFactory.create(this.value); BinaryValue thatValue = binaryFactory.create(that.value); return thisValue.equals(thatValue); case PropertyType.BOOLEAN: return this.getBoolean() == that.getBoolean(); case PropertyType.DOUBLE: return this.getDouble() == that.getDouble(); case PropertyType.LONG: return this.getLong() == that.getLong(); case PropertyType.DECIMAL: return getDecimal().equals(that.getDecimal()); case PropertyType.DATE: DateTimeFactory dateFactory = factories().getDateFactory(); DateTime thisDateValue = dateFactory.create(this.value); DateTime thatDateValue = dateFactory.create(that.value); return thisDateValue.equals(thatDateValue); case PropertyType.PATH: PathFactory pathFactory = factories().getPathFactory(); Path thisPathValue = pathFactory.create(this.value); Path thatPathValue = pathFactory.create(that.value); return thisPathValue.equals(thatPathValue); case PropertyType.NAME: NameFactory nameFactory = factories().getNameFactory(); Name thisNameValue = nameFactory.create(this.value); Name thatNameValue = nameFactory.create(that.value); return thisNameValue.equals(thatNameValue); case PropertyType.REFERENCE: case PropertyType.WEAKREFERENCE: case org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE: return this.getString().equals(that.getString()); case PropertyType.URI: return this.getString().equals(that.getString()); default: throw new SystemFailureException(JcrI18n.invalidPropertyType.text(this.type)); } } catch (RepositoryException e) { return false; } // will not get here } if (obj instanceof Value) { Value that = (Value)obj; if (this.type != that.getType()) return false; try { switch (this.type) { case PropertyType.STRING: return this.getString().equals(that.getString()); case PropertyType.BINARY: return IoUtil.isSame(this.getStream(), that.getBinary().getStream()); case PropertyType.BOOLEAN: return this.getBoolean() == that.getBoolean(); case PropertyType.DOUBLE: return this.getDouble() == that.getDouble(); case PropertyType.LONG: return this.getLong() == that.getLong(); case PropertyType.DECIMAL: return this.getDecimal().equals(that.getDecimal()); case PropertyType.DATE: return this.getDate().equals(that.getDate()); case PropertyType.PATH: return this.getString().equals(that.getString()); case PropertyType.NAME: return this.getString().equals(that.getString()); case PropertyType.REFERENCE: case PropertyType.WEAKREFERENCE: case org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE: return this.getString().equals(that.getString()); case PropertyType.URI: return this.getString().equals(that.getString()); default: throw new SystemFailureException(JcrI18n.invalidPropertyType.text(this.type)); } } catch (IOException e) { return false; } catch (RepositoryException e) { return false; } // will not get here } return false; } private JcrValue withTypeAndValue( int type, Object value ) { return new JcrValue(factories, type, value); } /** * Returns a copy of the current {@link JcrValue} cast to the JCR type specified by the <code>type</code> argument. This * method will attempt to convert the value (using the JCR type conversion rules) only if the existing type does not match the * supplied type, and if the conversion fails a {@link ValueFormatException} will be thrown. See the * {@link #asType(int, boolean)} method, which is the same as this method except that the conversion can be forced. * <p> * Calling this method is equivalent to calling: * * <pre> * asType(type, false); * </pre> * * </p> * * @param type the JCR type from {@link PropertyType} that the new {@link JcrValue} should have. * @return a new {@link JcrValue} with the given JCR type and an equivalent value. * @throws ValueFormatException if the value contained by this {@link JcrValue} cannot be converted to the desired type. * @see PropertyType * @see #asType(int, boolean) */ final JcrValue asType( int type ) throws ValueFormatException { return asType(type, false); } /** * Returns a copy of the current {@link JcrValue} cast to the JCR type specified by the <code>type</code> argument. If the * value cannot be converted base don the JCR type conversion rules, a {@link ValueFormatException} will be thrown. * * @param type the JCR type from {@link PropertyType} that the new {@link JcrValue} should have. * @param force true if the conversion should always be done, even if this value's type is the same as the expected type * @return a new {@link JcrValue} with the given JCR type and an equivalent value. * @throws ValueFormatException if the value contained by this {@link JcrValue} cannot be converted to the desired type. * @see PropertyType */ final JcrValue asType( int type, boolean force ) throws ValueFormatException { if (!force && type == this.type) { return this.withTypeAndValue(this.type, this.value); } Object value = this.value; switch (type) { case PropertyType.BOOLEAN: // Make sure the existing value is valid per the current type. // This is required if we rely upon the value factories to cast correctly. if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY) { throw createValueFormatException(boolean.class); } try { return this.withTypeAndValue(type, factories().getBooleanFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } case PropertyType.DATE: if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.DOUBLE && this.type != PropertyType.LONG) { throw createValueFormatException(Calendar.class); } try { return this.withTypeAndValue(type, factories().getDateFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } case PropertyType.NAME: if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.PATH) { throw createValueFormatException(Name.class); } try { return this.withTypeAndValue(type, factories().getNameFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } case PropertyType.PATH: if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.NAME) { throw createValueFormatException(Path.class); } try { return this.withTypeAndValue(type, factories().getPathFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } case PropertyType.REFERENCE: if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.WEAKREFERENCE) { throw createValueFormatException(Node.class); } try { return this.withTypeAndValue(type, factories().getReferenceFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } case PropertyType.WEAKREFERENCE: if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.REFERENCE) { throw createValueFormatException(Node.class); } try { return this.withTypeAndValue(type, factories().getWeakReferenceFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } case org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE: if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.REFERENCE && this.type != PropertyType.WEAKREFERENCE) { throw createValueFormatException(Node.class); } try { return this.withTypeAndValue(type, factories().getSimpleReferenceFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } case PropertyType.DOUBLE: if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.LONG && this.type != PropertyType.DATE) { throw createValueFormatException(double.class); } try { return this.withTypeAndValue(type, factories().getDoubleFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } case PropertyType.LONG: if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.DOUBLE && this.type != PropertyType.DATE) { throw createValueFormatException(long.class); } try { return this.withTypeAndValue(type, factories().getLongFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } case PropertyType.DECIMAL: if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.DOUBLE && this.type != PropertyType.LONG && this.type != PropertyType.DATE) { throw createValueFormatException(BigDecimal.class); } try { return this.withTypeAndValue(type, factories().getDecimalFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } case PropertyType.URI: if (this.type != PropertyType.STRING && this.type != PropertyType.BINARY && this.type != PropertyType.URI) { throw createValueFormatException(URI.class); } try { return this.withTypeAndValue(type, factories().getUriFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } // Anything can be converted to these types case PropertyType.BINARY: try { return this.withTypeAndValue(type, factories().getBinaryFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } case PropertyType.STRING: try { return this.withTypeAndValue(type, factories().getStringFactory().create(value)); } catch (org.modeshape.jcr.value.ValueFormatException vfe) { throw createValueFormatException(vfe); } case PropertyType.UNDEFINED: return this.withTypeAndValue(this.type, this.value); default: assert false : "Unexpected JCR property type " + type; // This should still throw an exception even if assertions are turned off throw new IllegalStateException("Invalid property type " + type); } } protected Object valueToType( int type, Value value ) throws RepositoryException { switch (type) { case PropertyType.BOOLEAN: return factories().getBooleanFactory().create(value.getBoolean()); case PropertyType.DATE: return factories().getDateFactory().create(value.getDate()); case PropertyType.NAME: return factories().getNameFactory().create(value.getString()); case PropertyType.PATH: return factories().getPathFactory().create(value.getString()); case PropertyType.REFERENCE: return factories().getReferenceFactory().create(value.getString()); case PropertyType.WEAKREFERENCE: return factories().getWeakReferenceFactory().create(value.getString()); case org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE: return factories().getSimpleReferenceFactory().create(value.getString()); case PropertyType.DOUBLE: return factories().getDoubleFactory().create(value.getDouble()); case PropertyType.LONG: return factories().getLongFactory().create(value.getLong()); case PropertyType.DECIMAL: return factories().getDecimalFactory().create(value.getDecimal()); case PropertyType.URI: return factories().getUriFactory().create(value.getString()); case PropertyType.BINARY: return factories().getBinaryFactory().create(value.getBinary()); case PropertyType.STRING: return factories().getStringFactory().create(value.getString()); case PropertyType.UNDEFINED: return value.getString(); default: assert false : "Unexpected JCR property type " + type; // This should still throw an exception even if assertions are turned off throw new IllegalStateException("Invalid property type " + type); } } protected Object convertToType( int type, Object value ) { if (value == null) return null; switch (type) { case PropertyType.BOOLEAN: return factories().getBooleanFactory().create(value); case PropertyType.DATE: return factories().getDateFactory().create(value); case PropertyType.NAME: return factories().getNameFactory().create(value); case PropertyType.PATH: return factories().getPathFactory().create(value); case PropertyType.REFERENCE: return factories().getReferenceFactory().create(value); case PropertyType.WEAKREFERENCE: return factories().getWeakReferenceFactory().create(value); case org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE: return factories().getSimpleReferenceFactory().create(value); case PropertyType.DOUBLE: return factories().getDoubleFactory().create(value); case PropertyType.LONG: return factories().getLongFactory().create(value); case PropertyType.DECIMAL: return factories().getDecimalFactory().create(value); case PropertyType.URI: return factories().getUriFactory().create(value); case PropertyType.BINARY: return factories().getBinaryFactory().create(value); case PropertyType.STRING: return factories().getStringFactory().create(value); case PropertyType.UNDEFINED: return value; default: assert false : "Unexpected JCR property type " + type; // This should still throw an exception even if assertions are turned off throw new IllegalStateException("Invalid property type " + type); } } @Override public String toString() { return value == null ? "null" : value.toString(); } }