| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.jackrabbit.oak.plugins.document; |
| |
| import static java.util.Collections.emptyList; |
| import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty; |
| |
| import java.util.List; |
| |
| import javax.jcr.PropertyType; |
| |
| import com.google.common.collect.Lists; |
| import org.apache.jackrabbit.oak.api.PropertyState; |
| import org.apache.jackrabbit.oak.api.Type; |
| import org.apache.jackrabbit.oak.commons.json.JsopReader; |
| import org.apache.jackrabbit.oak.commons.json.JsopTokenizer; |
| import org.apache.jackrabbit.oak.json.TypeCodes; |
| import org.apache.jackrabbit.oak.plugins.memory.AbstractPropertyState; |
| import org.apache.jackrabbit.oak.plugins.memory.BinaryPropertyState; |
| import org.apache.jackrabbit.oak.plugins.memory.BooleanPropertyState; |
| import org.apache.jackrabbit.oak.plugins.memory.DoublePropertyState; |
| import org.apache.jackrabbit.oak.plugins.memory.LongPropertyState; |
| import org.apache.jackrabbit.oak.plugins.memory.PropertyStates; |
| import org.apache.jackrabbit.oak.plugins.memory.StringPropertyState; |
| import org.apache.jackrabbit.oak.plugins.value.Conversions; |
| import org.jetbrains.annotations.NotNull; |
| |
| /** |
| * PropertyState implementation with lazy parsing of the JSOP encoded value. |
| */ |
| final class DocumentPropertyState implements PropertyState { |
| |
| private final DocumentNodeStore store; |
| |
| private final String name; |
| |
| private final String value; |
| |
| private PropertyState parsed; |
| |
| DocumentPropertyState(DocumentNodeStore store, String name, String value) { |
| this.store = store; |
| this.name = name; |
| this.value = value; |
| } |
| |
| @NotNull |
| @Override |
| public String getName() { |
| return name; |
| } |
| |
| @Override |
| public boolean isArray() { |
| return parsed().isArray(); |
| } |
| |
| @Override |
| public Type<?> getType() { |
| return parsed().getType(); |
| } |
| |
| @NotNull |
| @Override |
| public <T> T getValue(Type<T> type) { |
| return parsed().getValue(type); |
| } |
| |
| @NotNull |
| @Override |
| public <T> T getValue(Type<T> type, int index) { |
| return parsed().getValue(type, index); |
| } |
| |
| @Override |
| public long size() { |
| return parsed().size(); |
| } |
| |
| @Override |
| public long size(int index) { |
| long size; |
| PropertyState parsed = parsed(); |
| if (parsed.getType() == Type.BINARIES) { |
| size = parsed.getValue(Type.BINARY, index).length(); |
| } else { |
| size = parsed.size(index); |
| } |
| return size; |
| } |
| |
| @Override |
| public int count() { |
| return parsed().count(); |
| } |
| |
| /** |
| * Returns the raw un-parsed value as passed to the constructor of this |
| * property state. |
| * |
| * @return the raw un-parsed value. |
| */ |
| @NotNull |
| String getValue() { |
| return value; |
| } |
| |
| //------------------------------------------------------------< Object >-- |
| |
| @Override |
| public boolean equals(Object object) { |
| if (this == object) { |
| return true; |
| } else if (object instanceof DocumentPropertyState) { |
| DocumentPropertyState other = (DocumentPropertyState) object; |
| return this.name.equals(other.name) |
| && this.value.equals(other.value); |
| } |
| // fall back to default equality check in AbstractPropertyState |
| return object instanceof PropertyState |
| && AbstractPropertyState.equal(parsed(), (PropertyState) object); |
| } |
| |
| @Override |
| public int hashCode() { |
| return AbstractPropertyState.hashCode(this); |
| } |
| |
| @Override |
| public String toString() { |
| return AbstractPropertyState.toString(this); |
| } |
| |
| //----------------------------< internal >---------------------------------- |
| |
| private PropertyState parsed() { |
| if (parsed == null) { |
| JsopReader reader = new JsopTokenizer(value); |
| if (reader.matches('[')) { |
| parsed = readArrayProperty(name, reader); |
| } else { |
| parsed = readProperty(name, reader); |
| } |
| } |
| return parsed; |
| } |
| |
| /** |
| * Read a {@code PropertyState} from a {@link JsopReader} |
| * @param name The name of the property state |
| * @param reader The reader |
| * @return new property state |
| */ |
| PropertyState readProperty(String name, JsopReader reader) { |
| return readProperty(name, store, reader); |
| } |
| |
| /** |
| * Read a {@code PropertyState} from a {@link JsopReader}. |
| * |
| * @param name the name of the property state |
| * @param store the store |
| * @param reader the reader |
| * @return new property state |
| */ |
| static PropertyState readProperty(String name, DocumentNodeStore store, JsopReader reader) { |
| if (reader.matches(JsopReader.NUMBER)) { |
| String number = reader.getToken(); |
| try { |
| return new LongPropertyState(name, Long.parseLong(number)); |
| } catch (NumberFormatException e) { |
| return new DoublePropertyState(name, Double.parseDouble(number)); |
| } |
| } else if (reader.matches(JsopReader.TRUE)) { |
| return BooleanPropertyState.booleanProperty(name, true); |
| } else if (reader.matches(JsopReader.FALSE)) { |
| return BooleanPropertyState.booleanProperty(name, false); |
| } else if (reader.matches(JsopReader.STRING)) { |
| String jsonString = reader.getToken(); |
| if (jsonString.startsWith(TypeCodes.EMPTY_ARRAY)) { |
| int type = PropertyType.valueFromName(jsonString.substring(TypeCodes.EMPTY_ARRAY.length())); |
| return PropertyStates.createProperty(name, emptyList(), Type.fromTag(type, true)); |
| } |
| int split = TypeCodes.split(jsonString); |
| if (split != -1) { |
| int type = TypeCodes.decodeType(split, jsonString); |
| String value = TypeCodes.decodeName(split, jsonString); |
| if (type == PropertyType.BINARY) { |
| |
| return BinaryPropertyState.binaryProperty(name, store.getBlobFromBlobId(value)); |
| } else { |
| return createProperty(name, StringCache.get(value), type); |
| } |
| } else { |
| return StringPropertyState.stringProperty(name, StringCache.get(jsonString)); |
| } |
| } else { |
| throw new IllegalArgumentException("Unexpected token: " + reader.getToken()); |
| } |
| } |
| |
| /** |
| * Read a multi valued {@code PropertyState} from a {@link JsopReader}. |
| * |
| * @param name the name of the property state |
| * @param reader the reader |
| * @return new property state |
| */ |
| PropertyState readArrayProperty(String name, JsopReader reader) { |
| return readArrayProperty(name, store, reader); |
| } |
| |
| /** |
| * Read a multi valued {@code PropertyState} from a {@link JsopReader}. |
| * |
| * @param name the name of the property state |
| * @param store the store |
| * @param reader the reader |
| * @return new property state |
| */ |
| static PropertyState readArrayProperty(String name, DocumentNodeStore store, JsopReader reader) { |
| int type = PropertyType.STRING; |
| List<Object> values = Lists.newArrayList(); |
| while (!reader.matches(']')) { |
| if (reader.matches(JsopReader.NUMBER)) { |
| String number = reader.getToken(); |
| try { |
| type = PropertyType.LONG; |
| values.add(Long.parseLong(number)); |
| } catch (NumberFormatException e) { |
| type = PropertyType.DOUBLE; |
| values.add(Double.parseDouble(number)); |
| } |
| } else if (reader.matches(JsopReader.TRUE)) { |
| type = PropertyType.BOOLEAN; |
| values.add(true); |
| } else if (reader.matches(JsopReader.FALSE)) { |
| type = PropertyType.BOOLEAN; |
| values.add(false); |
| } else if (reader.matches(JsopReader.STRING)) { |
| String jsonString = reader.getToken(); |
| int split = TypeCodes.split(jsonString); |
| if (split != -1) { |
| type = TypeCodes.decodeType(split, jsonString); |
| String value = TypeCodes.decodeName(split, jsonString); |
| if (type == PropertyType.BINARY) { |
| values.add(store.getBlobFromBlobId(value)); |
| } else if (type == PropertyType.DOUBLE) { |
| values.add(Conversions.convert(value).toDouble()); |
| } else if (type == PropertyType.DECIMAL) { |
| values.add(Conversions.convert(value).toDecimal()); |
| } else { |
| values.add(StringCache.get(value)); |
| } |
| } else { |
| type = PropertyType.STRING; |
| values.add(StringCache.get(jsonString)); |
| } |
| } else { |
| throw new IllegalArgumentException("Unexpected token: " + reader.getToken()); |
| } |
| reader.matches(','); |
| } |
| return createProperty(name, values, Type.fromTag(type, true)); |
| } |
| } |