All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.oak.segment.Template Maven / Gradle / Ivy

There is a newer version: 1.72.0
Show newest version
/*
 * 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.segment;

import static com.google.common.base.Preconditions.checkElementIndex;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.jackrabbit.oak.api.Type.STRING;
import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.MISSING_NODE;
import static org.apache.jackrabbit.oak.segment.Segment.RECORD_ID_BYTES;
import static org.apache.jackrabbit.oak.segment.CacheWeights.OBJECT_HEADER_SIZE;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import com.google.common.base.Objects;
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.StringUtils;
import org.apache.jackrabbit.oak.plugins.memory.MemoryChildNodeEntry;
import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * The in-memory representation of a "hidden class" of a node; inspired by the
 * Chrome V8 Javascript engine).
 * 

* Templates are always read fully in-memory. */ public class Template { static final short ZERO_CHILD_NODES_TYPE = 0; static final short SINGLE_CHILD_NODE_TYPE = 1; static final short MANY_CHILD_NODES_TYPE = 2; static final String ZERO_CHILD_NODES = null; static final String MANY_CHILD_NODES = ""; @NotNull private final SegmentReader reader; /** * The {@code jcr:primaryType} property, if present as a single-valued * {@code NAME} property. Otherwise {@code null}. */ @Nullable private final PropertyState primaryType; /** * The {@code jcr:mixinTypes} property, if present as a multi-valued * {@code NAME} property. Otherwise {@code null}. */ @Nullable private final PropertyState mixinTypes; /** * Templates of all the properties of a node, excluding the * above-mentioned {@code NAME}-valued type properties, if any. */ @NotNull private final PropertyTemplate[] properties; /** * Name of the single child node, if the node contains just one child. * Otherwise {@link #ZERO_CHILD_NODES} (i.e. {@code null}) if there are * no children, or {@link #MANY_CHILD_NODES} if there are more than one. */ @Nullable private final String childName; Template(@NotNull SegmentReader reader, @Nullable PropertyState primaryType, @Nullable PropertyState mixinTypes, @Nullable PropertyTemplate[] properties, @Nullable String childName) { this.reader = checkNotNull(reader); this.primaryType = primaryType; this.mixinTypes = mixinTypes; if (properties != null) { this.properties = properties; Arrays.sort(this.properties); } else { this.properties = new PropertyTemplate[0]; } this.childName = childName; } Template(@NotNull SegmentReader reader, @NotNull NodeState state) { this.reader = checkNotNull(reader); checkNotNull(state); PropertyState primary = null; PropertyState mixins = null; List templates = Lists.newArrayList(); for (PropertyState property : state.getProperties()) { String name = property.getName(); Type type = property.getType(); if ("jcr:primaryType".equals(name) && type == Type.NAME) { primary = property; } else if ("jcr:mixinTypes".equals(name) && type == Type.NAMES) { mixins = property; } else { templates.add(new PropertyTemplate(property)); } } this.primaryType = primary; this.mixinTypes = mixins; this.properties = templates.toArray(new PropertyTemplate[templates.size()]); Arrays.sort(properties); long count = state.getChildNodeCount(2); if (count == 0) { childName = ZERO_CHILD_NODES; } else if (count == 1) { childName = state.getChildNodeNames().iterator().next(); checkState(childName != null && !childName.equals(MANY_CHILD_NODES)); } else { childName = MANY_CHILD_NODES; } } @Nullable PropertyState getPrimaryType() { return primaryType; } @Nullable PropertyState getMixinTypes() { return mixinTypes; } PropertyTemplate[] getPropertyTemplates() { return properties; } /** * Returns the template of the named property, or {@code null} if no such * property exists. Use the {@link #getPrimaryType()} and * {@link #getMixinTypes()} for accessing the JCR type properties, as * they don't have templates. * * @param name property name * @return property template, or {@code} null if not found */ PropertyTemplate getPropertyTemplate(String name) { int hash = name.hashCode(); int index = 0; while (index < properties.length && properties[index].getName().hashCode() < hash) { index++; } while (index < properties.length && properties[index].getName().hashCode() == hash) { if (name.equals(properties[index].getName())) { return properties[index]; } index++; } return null; } @Nullable String getChildName() { return childName; } SegmentPropertyState getProperty(RecordId recordId, int index) { checkElementIndex(index, properties.length); Segment segment = checkNotNull(recordId).getSegment(); int offset = 2 * RECORD_ID_BYTES; if (childName != ZERO_CHILD_NODES) { offset += RECORD_ID_BYTES; } RecordId lid = segment.readRecordId(recordId.getRecordNumber(), offset); ListRecord props = new ListRecord(lid, properties.length); RecordId rid = props.getEntry(index); return reader.readProperty(rid, properties[index]); } MapRecord getChildNodeMap(RecordId recordId) { checkState(childName != ZERO_CHILD_NODES); Segment segment = recordId.getSegment(); RecordId childNodesId = segment.readRecordId(recordId.getRecordNumber(), 2 * RECORD_ID_BYTES); return reader.readMap(childNodesId); } public NodeState getChildNode(String name, RecordId recordId) { if (childName == ZERO_CHILD_NODES) { return MISSING_NODE; } else if (childName == MANY_CHILD_NODES) { MapRecord map = getChildNodeMap(recordId); MapEntry child = map.getEntry(name); if (child != null) { return child.getNodeState(); } else { return MISSING_NODE; } } else if (name.equals(childName)) { Segment segment = recordId.getSegment(); RecordId childNodeId = segment.readRecordId(recordId.getRecordNumber(), 2 * RECORD_ID_BYTES); return reader.readNode(childNodeId); } else { return MISSING_NODE; } } Iterable getChildNodeEntries(RecordId recordId) { if (childName == ZERO_CHILD_NODES) { return Collections.emptyList(); } else if (childName == MANY_CHILD_NODES) { MapRecord map = getChildNodeMap(recordId); return map.getEntries(); } else { Segment segment = recordId.getSegment(); RecordId childNodeId = segment.readRecordId(recordId.getRecordNumber(), 2 * RECORD_ID_BYTES); return Collections.singletonList(new MemoryChildNodeEntry( childName, reader.readNode(childNodeId))); } } public boolean compare(RecordId thisId, RecordId thatId) { checkNotNull(thisId); checkNotNull(thatId); // Compare properties for (int i = 0; i < properties.length; i++) { PropertyState thisProperty = getProperty(thisId, i); PropertyState thatProperty = getProperty(thatId, i); if (!thisProperty.equals(thatProperty)) { return false; } } // Compare child nodes if (childName == ZERO_CHILD_NODES) { return true; } else if (childName != MANY_CHILD_NODES) { NodeState thisChild = getChildNode(childName, thisId); NodeState thatChild = getChildNode(childName, thatId); return thisChild.equals(thatChild); } else { // TODO: Leverage the HAMT data structure for the comparison MapRecord thisMap = getChildNodeMap(thisId); MapRecord thatMap = getChildNodeMap(thatId); if (Record.fastEquals(thisMap, thatMap)) { return true; // shortcut } else if (thisMap.size() != thatMap.size()) { return false; // shortcut } else { // TODO: can this be optimized? for (MapEntry entry : thisMap.getEntries()) { String name = entry.getName(); MapEntry thatEntry = thatMap.getEntry(name); if (thatEntry == null) { return false; } else if (!entry.getNodeState().equals(thatEntry.getNodeState())) { return false; } } return true; } } } //------------------------------------------------------------< Object >-- @Override public boolean equals(Object object) { if (this == object) { return true; } else if (object instanceof Template) { Template that = (Template) object; return Objects.equal(primaryType, that.primaryType) && Objects.equal(mixinTypes, that.mixinTypes) && Arrays.equals(properties, that.properties) && Objects.equal(childName, that.childName); } else { return false; } } @Override public int hashCode() { return Objects.hashCode(primaryType, mixinTypes, Arrays.asList(properties), getTemplateType(), childName); } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("{ "); if (primaryType != null) { builder.append(primaryType); builder.append(", "); } if (mixinTypes != null) { builder.append(mixinTypes); builder.append(", "); } for (PropertyTemplate property : properties) { builder.append(property); builder.append(" = ?, "); } if (childName == ZERO_CHILD_NODES) { builder.append(""); } else if (childName == MANY_CHILD_NODES) { builder.append(""); } else { builder.append(childName + " = "); } builder.append(" }"); return builder.toString(); } short getTemplateType() { if (childName == ZERO_CHILD_NODES) { return ZERO_CHILD_NODES_TYPE; } else if (childName == MANY_CHILD_NODES) { return MANY_CHILD_NODES_TYPE; } else { return SINGLE_CHILD_NODE_TYPE; } } public int estimateMemoryUsage() { int size = OBJECT_HEADER_SIZE; size += 48; size += StringUtils.estimateMemoryUsage(childName); size += estimateMemoryUsage(mixinTypes); size += estimateMemoryUsage(primaryType); for (PropertyTemplate property : properties) { size += property.estimateMemoryUsage(); } return size; } private static int estimateMemoryUsage(PropertyState propertyState) { if (propertyState == null) { return 0; } int size = OBJECT_HEADER_SIZE; size += StringUtils.estimateMemoryUsage(propertyState.getName()); for (int k = 0; k < propertyState.count(); k++) { String s = propertyState.getValue(STRING, k); size += StringUtils.estimateMemoryUsage(s); } return size; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy