| /* |
| * 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.explorer; |
| |
| import static com.google.common.collect.Lists.newArrayList; |
| import static com.google.common.collect.Maps.newHashMap; |
| import static com.google.common.collect.Maps.newTreeMap; |
| import static com.google.common.collect.Sets.intersection; |
| import static com.google.common.collect.Sets.newHashSet; |
| import static com.google.common.collect.Sets.newTreeSet; |
| import static com.google.common.escape.Escapers.builder; |
| import static java.util.Collections.sort; |
| import static javax.jcr.PropertyType.BINARY; |
| import static javax.jcr.PropertyType.STRING; |
| import static javax.swing.tree.TreeSelectionModel.SINGLE_TREE_SELECTION; |
| import static org.apache.commons.io.FileUtils.byteCountToDisplaySize; |
| import static org.apache.jackrabbit.oak.commons.PathUtils.concat; |
| import static org.apache.jackrabbit.oak.commons.PathUtils.elements; |
| import static org.apache.jackrabbit.oak.commons.json.JsopBuilder.prettyPrint; |
| import static org.apache.jackrabbit.oak.json.JsopDiff.diffToJsop; |
| |
| import java.awt.*; |
| import java.io.Closeable; |
| import java.io.IOException; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.UUID; |
| |
| import javax.swing.*; |
| import javax.swing.event.TreeSelectionEvent; |
| import javax.swing.event.TreeSelectionListener; |
| import javax.swing.tree.DefaultMutableTreeNode; |
| import javax.swing.tree.DefaultTreeModel; |
| |
| import org.apache.jackrabbit.oak.api.Blob; |
| import org.apache.jackrabbit.oak.api.PropertyState; |
| import org.apache.jackrabbit.oak.api.Type; |
| import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; |
| import org.apache.jackrabbit.oak.spi.state.NodeState; |
| |
| class NodeStoreTree extends JPanel implements TreeSelectionListener, Closeable { |
| |
| private static final long serialVersionUID = 1L; |
| |
| private final static int MAX_CHAR_DISPLAY = Integer.getInteger("max.char.display", 60); |
| |
| private static final String newline = "\n"; |
| |
| private static void printGcRoots(StringBuilder sb, Map<UUID, Set<Entry<UUID, String>>> links, UUID uuid, String space, String inc) { |
| Set<Entry<UUID, String>> roots = links.remove(uuid); |
| |
| if (roots == null || roots.isEmpty()) { |
| return; |
| } |
| |
| // TODO is sorting by file name needed? |
| for (Entry<UUID, String> r : roots) { |
| sb.append(space).append(r.getKey()).append("[").append(r.getValue()).append("]").append(newline); |
| printGcRoots(sb, links, r.getKey(), space + inc, inc); |
| } |
| } |
| |
| private static void printPaths(List<String> paths, StringBuilder sb) { |
| if (paths.isEmpty()) { |
| return; |
| } |
| |
| sb.append("Repository content references:").append(newline); |
| |
| for (String p : paths) { |
| sb.append(p).append(newline); |
| } |
| } |
| |
| private static void printPropertyState(ExplorerBackend store, PropertyState state, String parentFile, StringBuilder b) { |
| if (store.isPersisted(state)) { |
| printRecordId(store.getRecordId(state), store.getFile(state), parentFile, b); |
| } else { |
| printSimpleClassName(state, b); |
| } |
| } |
| |
| private static void printNodeState(ExplorerBackend store, NodeState state, String parentTarFile, StringBuilder b) { |
| if (store.isPersisted(state)) { |
| printRecordId(store.getRecordId(state), store.getFile(state), parentTarFile, b); |
| } else { |
| printSimpleClassName(state, b); |
| } |
| } |
| |
| private static void printRecordId(String recordId, String file, String parentFile, StringBuilder l) { |
| l.append(" (").append(recordId); |
| |
| if (file != null && !file.equals(parentFile)) { |
| l.append(" in ").append(file); |
| } |
| |
| l.append(")"); |
| } |
| |
| private static void printSimpleClassName(Object o, StringBuilder l) { |
| l.append(" (").append(o.getClass().getSimpleName()).append(")"); |
| } |
| |
| private final ExplorerBackend backend; |
| |
| private Map<String, Set<UUID>> index; |
| |
| private DefaultTreeModel treeModel; |
| |
| private final JTree tree; |
| |
| private final JTextArea log; |
| |
| private Map<String, Long[]> sizeCache; |
| |
| private final boolean skipSizeCheck; |
| |
| NodeStoreTree(ExplorerBackend backend, JTextArea log, boolean skipSizeCheck) |
| throws IOException { |
| super(new GridLayout(1, 0)); |
| this.backend = backend; |
| this.log = log; |
| this.skipSizeCheck = skipSizeCheck; |
| |
| tree = new JTree(); |
| tree.getSelectionModel().setSelectionMode(SINGLE_TREE_SELECTION); |
| tree.setShowsRootHandles(true); |
| tree.addTreeSelectionListener(this); |
| tree.setExpandsSelectedPaths(true); |
| |
| refreshStore(); |
| refreshModel(); |
| |
| JScrollPane scrollPane = new JScrollPane(tree); |
| add(scrollPane); |
| } |
| |
| private void refreshStore() throws IOException { |
| backend.open(); |
| } |
| |
| private void refreshModel() { |
| index = backend.getTarReaderIndex(); |
| sizeCache = newHashMap(); |
| DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode( |
| new NamePathModel("/", "/", backend.getHead(), sizeCache, |
| skipSizeCheck, backend), true); |
| treeModel = new DefaultTreeModel(rootNode); |
| addChildren(rootNode); |
| tree.setModel(treeModel); |
| } |
| |
| void reopen() throws IOException { |
| close(); |
| refreshStore(); |
| refreshModel(); |
| } |
| |
| @Override |
| public void valueChanged(TreeSelectionEvent e) { |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree |
| .getLastSelectedPathComponent(); |
| if (node == null) { |
| return; |
| } |
| // load child nodes: |
| try { |
| addChildren(node); |
| updateStats(node); |
| } catch (IllegalStateException ex) { |
| ex.printStackTrace(); |
| |
| StringBuilder sb = new StringBuilder(); |
| sb.append(ex.getMessage()); |
| sb.append(newline); |
| |
| NamePathModel model = (NamePathModel) node.getUserObject(); |
| NodeState state = model.getState(); |
| String recordId = backend.getRecordId(state); |
| if (recordId != null) { |
| sb.append("Record "); |
| sb.append(recordId); |
| sb.append(newline); |
| } |
| setText(sb.toString()); |
| } |
| } |
| |
| private void setText(String s) { |
| log.setText(s); |
| log.setCaretPosition(0); |
| } |
| |
| private void addChildren(DefaultMutableTreeNode parent) { |
| NamePathModel model = (NamePathModel) parent.getUserObject(); |
| if (model.isLoaded()) { |
| return; |
| } |
| |
| List<NamePathModel> kids = newArrayList(); |
| for (ChildNodeEntry ce : model.getState().getChildNodeEntries()) { |
| NamePathModel c = new NamePathModel(ce.getName(), concat( |
| model.getPath(), ce.getName()), ce.getNodeState(), |
| sizeCache, skipSizeCheck, backend); |
| kids.add(c); |
| } |
| sort(kids); |
| for (NamePathModel c : kids) { |
| DefaultMutableTreeNode childNode = new DefaultMutableTreeNode(c, |
| true); |
| treeModel.insertNodeInto(childNode, parent, parent.getChildCount()); |
| } |
| model.loaded(); |
| } |
| |
| private void updateStats(DefaultMutableTreeNode parent) { |
| NamePathModel model = (NamePathModel) parent.getUserObject(); |
| |
| StringBuilder sb = new StringBuilder(); |
| sb.append(model.getPath()); |
| sb.append(newline); |
| |
| NodeState state = model.getState(); |
| String tarFile = ""; |
| |
| if (backend.isPersisted(state)) { |
| String recordId = backend.getRecordId(state); |
| sb.append("Record ").append(recordId); |
| tarFile = backend.getFile(state); |
| if (tarFile != null) { |
| sb.append(" in ").append(tarFile); |
| } |
| sb.append(newline); |
| |
| String templateId = backend.getTemplateRecordId(state); |
| String f = backend.getTemplateFile(state); |
| sb.append("TemplateId "); |
| sb.append(templateId); |
| if (f != null && !f.equals(tarFile)) { |
| sb.append(" in ").append(f); |
| } |
| sb.append(newline); |
| } |
| |
| sb.append("Size: "); |
| sb.append(" direct: "); |
| sb.append(byteCountToDisplaySize(model.getSize()[0])); |
| sb.append("; linked: "); |
| sb.append(byteCountToDisplaySize(model.getSize()[1])); |
| sb.append(newline); |
| |
| sb.append("Properties (count: ").append(state.getPropertyCount()).append(")"); |
| sb.append(newline); |
| Map<String, String> propLines = newTreeMap(); |
| for (PropertyState ps : state.getProperties()) { |
| StringBuilder l = new StringBuilder(); |
| l.append(" - ").append(ps.getName()).append(" = {").append(ps.getType()).append("} "); |
| if (ps.getType().isArray()) { |
| int count = ps.count(); |
| l.append("(count ").append(count).append(") ["); |
| |
| String separator = ", "; |
| int max = 50; |
| if (ps.getType() == Type.BINARIES) { |
| separator = newline + " "; |
| max = Integer.MAX_VALUE; |
| l.append(separator); |
| } |
| for (int i = 0; i < Math.min(count, max); i++) { |
| if (i > 0) { |
| l.append(separator); |
| } |
| l.append(toString(ps, i, tarFile)); |
| } |
| if (count > max) { |
| l.append(", ... (").append(count).append(" values)"); |
| } |
| if (ps.getType() == Type.BINARIES) { |
| l.append(separator); |
| } |
| l.append("]"); |
| |
| } else { |
| l.append(toString(ps, 0, tarFile)); |
| } |
| printPropertyState(backend, ps, tarFile, l); |
| propLines.put(ps.getName(), l.toString()); |
| } |
| |
| for (String l : propLines.values()) { |
| sb.append(l); |
| sb.append(newline); |
| } |
| |
| sb.append("Child nodes (count: ").append(state.getChildNodeCount(Long.MAX_VALUE)).append(")"); |
| sb.append(newline); |
| Map<String, String> childLines = newTreeMap(); |
| for (ChildNodeEntry ce : state.getChildNodeEntries()) { |
| StringBuilder l = new StringBuilder(); |
| l.append(" + ").append(ce.getName()); |
| NodeState c = ce.getNodeState(); |
| printNodeState(backend, c, tarFile, l); |
| childLines.put(ce.getName(), l.toString()); |
| } |
| for (String l : childLines.values()) { |
| sb.append(l); |
| sb.append(newline); |
| } |
| |
| if ("/".equals(model.getPath())) { |
| sb.append("File Reader Index"); |
| sb.append(newline); |
| |
| for (String path : backend.getTarFiles()) { |
| sb.append(path); |
| sb.append(newline); |
| } |
| sb.append("----------"); |
| } |
| |
| setText(sb.toString()); |
| } |
| |
| private String toString(PropertyState ps, int index, String tarFile) { |
| if (ps.getType().tag() == BINARY) { |
| Blob b = ps.getValue(Type.BINARY, index); |
| String info = "<"; |
| info += b.getClass().getSimpleName() + ";"; |
| info += "ref:" + safeGetReference(b) + ";"; |
| info += "id:" + b.getContentIdentity() + ";"; |
| info += safeGetLength(b) + ">"; |
| |
| for (Entry<UUID, String> e : backend.getBulkSegmentIds(b).entrySet()) { |
| info += newline + " Bulk Segment Id " + e.getKey(); |
| if (e.getValue() != null && !e.getValue().equals(tarFile)) { |
| info += " in " + e.getValue(); |
| } |
| } |
| |
| return info; |
| } else if (ps.getType().tag() == STRING) { |
| return displayString(ps.getValue(Type.STRING, index)); |
| } else { |
| return ps.getValue(Type.STRING, index); |
| } |
| } |
| |
| private static String displayString(String value) { |
| if (MAX_CHAR_DISPLAY > 0 && value.length() > MAX_CHAR_DISPLAY) { |
| value = value.substring(0, MAX_CHAR_DISPLAY) + "... (" |
| + value.length() + " chars)"; |
| } |
| String escaped = builder().setSafeRange(' ', '~') |
| .addEscape('"', "\\\"").addEscape('\\', "\\\\").build() |
| .escape(value); |
| return '"' + escaped + '"'; |
| } |
| |
| private String safeGetReference(Blob b) { |
| try { |
| return b.getReference(); |
| } catch (IllegalStateException e) { |
| // missing BlobStore probably |
| } |
| return "[BlobStore not available]"; |
| } |
| |
| private String safeGetLength(Blob b) { |
| try { |
| return byteCountToDisplaySize(b.length()); |
| } catch (IllegalStateException e) { |
| // missing BlobStore probably |
| } |
| return "[BlobStore not available]"; |
| } |
| |
| void printTarInfo(String file) { |
| if (file == null || file.length() == 0) { |
| return; |
| } |
| StringBuilder sb = new StringBuilder(); |
| |
| Set<UUID> uuids = newHashSet(); |
| for (Entry<String, Set<UUID>> e : index.entrySet()) { |
| if (e.getKey().endsWith(file)) { |
| sb.append("SegmentNodeState references to ").append(e.getKey()); |
| sb.append(newline); |
| uuids = e.getValue(); |
| break; |
| } |
| } |
| |
| Set<UUID> inMem = intersection(backend.getReferencedSegmentIds(), uuids); |
| if (!inMem.isEmpty()) { |
| sb.append("In Memory segment references: "); |
| sb.append(newline); |
| sb.append(inMem); |
| sb.append(newline); |
| } |
| |
| List<String> paths = newArrayList(); |
| filterNodeStates(uuids, paths, backend.getHead(), "/", backend); |
| printPaths(paths, sb); |
| |
| sb.append(newline); |
| try { |
| Map<UUID, Set<UUID>> graph = backend.getTarGraph(file); |
| sb.append("Tar graph:").append(newline); |
| for (Entry<UUID, Set<UUID>> entry : graph.entrySet()) { |
| sb.append(entry.getKey()).append('=').append(entry.getValue()) |
| .append(newline); |
| } |
| sb.append(newline); |
| } catch (IOException e) { |
| sb.append("Error getting tar graph:").append(e).append(newline); |
| } |
| |
| setText(sb.toString()); |
| } |
| |
| void printSegmentReferences(String sid) { |
| if (sid == null || sid.length() == 0) { |
| return; |
| } |
| UUID id = null; |
| try { |
| id = UUID.fromString(sid.trim()); |
| } catch (IllegalArgumentException e) { |
| setText(e.getMessage()); |
| return; |
| } |
| StringBuilder sb = new StringBuilder(); |
| sb.append("References to segment ").append(id); |
| sb.append(newline); |
| for (Entry<String, Set<UUID>> e : index.entrySet()) { |
| if (e.getValue().contains(id)) { |
| sb.append("Tar file: ").append(e.getKey()); |
| sb.append(newline); |
| break; |
| } |
| } |
| |
| List<String> paths = newArrayList(); |
| filterNodeStates(newHashSet(id), paths, backend.getHead(), "/", backend); |
| printPaths(paths, sb); |
| |
| Map<UUID, Set<Entry<UUID, String>>> links = newHashMap(); |
| try { |
| backend.getGcRoots(id, links); |
| } catch (IOException e) { |
| sb.append(newline); |
| sb.append(e.getMessage()); |
| } |
| if (!links.isEmpty()) { |
| sb.append("Segment GC roots:"); |
| sb.append(newline); |
| printGcRoots(sb, links, id, " ", " "); |
| } |
| |
| setText(sb.toString()); |
| } |
| |
| private static void filterNodeStates(Set<UUID> uuids, List<String> paths, NodeState state, String path, ExplorerBackend store) { |
| Set<String> localPaths = newTreeSet(); |
| for (PropertyState ps : state.getProperties()) { |
| if (store.isPersisted(ps)) { |
| String recordId = store.getRecordId(ps); |
| UUID id = store.getSegmentId(ps); |
| if (uuids.contains(id)) { |
| if (ps.getType().tag() == STRING) { |
| String val = ""; |
| if (ps.count() > 0) { |
| // only shows the first value, do we need more? |
| val = displayString(ps.getValue(Type.STRING, 0)); |
| } |
| localPaths.add(path + ps.getName() + " = " + val |
| + " [SegmentPropertyState<" + ps.getType() |
| + ">@" + recordId + "]"); |
| } else { |
| localPaths.add(path + ps + " [SegmentPropertyState<" |
| + ps.getType() + ">@" + recordId + "]"); |
| } |
| |
| } |
| if (ps.getType().tag() == BINARY) { |
| // look for extra segment references |
| for (int i = 0; i < ps.count(); i++) { |
| Blob b = ps.getValue(Type.BINARY, i); |
| for (Entry<UUID, String> e : store.getBulkSegmentIds(b).entrySet()) { |
| if (!e.getKey().equals(id) && uuids.contains(e.getKey())) { |
| localPaths.add(path + ps |
| + " [SegmentPropertyState<" |
| + ps.getType() + ">@" + recordId + "]"); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| String stateId = store.getRecordId(state); |
| if (uuids.contains(store.getSegmentId(state))) { |
| localPaths.add(path + " [SegmentNodeState@" + stateId + "]"); |
| } |
| |
| String templateId = store.getTemplateRecordId(state); |
| if (uuids.contains(store.getTemplateSegmentId(state))) { |
| localPaths.add(path + "[Template@" + templateId + "]"); |
| } |
| paths.addAll(localPaths); |
| for (ChildNodeEntry ce : state.getChildNodeEntries()) { |
| filterNodeStates(uuids, paths, ce.getNodeState(), path + ce.getName() + "/", store); |
| } |
| } |
| |
| void printDiff(String input) { |
| StringBuilder sb = new StringBuilder(); |
| if (input == null || input.trim().length() == 0) { |
| setText("Usage <recordId> <recordId> [<path>]"); |
| return; |
| } |
| |
| String[] tokens = input.trim().split(" "); |
| if (tokens.length != 2 && tokens.length != 3) { |
| setText("Usage <recordId> <recordId> [<path>]"); |
| return; |
| } |
| NodeState node1; |
| NodeState node2; |
| try { |
| node1 = backend.readNodeState(tokens[0]); |
| node2 = backend.readNodeState(tokens[1]); |
| } catch (IllegalArgumentException ex) { |
| sb.append("Unknown argument: "); |
| sb.append(input); |
| sb.append(newline); |
| sb.append("Error: "); |
| sb.append(ex.getMessage()); |
| sb.append(newline); |
| setText(sb.toString()); |
| return; |
| } |
| String path = "/"; |
| if (tokens.length == 3) { |
| path = tokens[2]; |
| } |
| |
| for (String name : elements(path)) { |
| node1 = node1.getChildNode(name); |
| node2 = node2.getChildNode(name); |
| } |
| |
| sb.append("SegmentNodeState diff "); |
| sb.append(tokens[0]); |
| sb.append(" vs "); |
| sb.append(tokens[1]); |
| sb.append(" at "); |
| sb.append(path); |
| sb.append(newline); |
| sb.append("--------"); |
| sb.append(newline); |
| sb.append(prettyPrint(diffToJsop(node1, node2))); |
| setText(sb.toString()); |
| } |
| |
| boolean revert(String revision) { |
| return safeRevert(revision, false); |
| } |
| |
| private boolean safeRevert(String revision, boolean rollback) { |
| String head = backend.getRecordId(backend.getHead()); |
| backend.setRevision(revision); |
| try { |
| refreshModel(); |
| if (!rollback) { |
| setText("Switched head revision to " + revision); |
| } |
| } catch (Exception e) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Unable to switch head revision to "); |
| sb.append(revision); |
| sb.append(newline); |
| sb.append(" "); |
| sb.append(e.getMessage()); |
| sb.append(newline); |
| sb.append("Will rollback to "); |
| sb.append(head); |
| setText(sb.toString()); |
| return safeRevert(head, true); |
| } |
| return !rollback; |
| } |
| |
| void printPCMInfo() { |
| setText(backend.getPersistedCompactionMapStats()); |
| } |
| |
| private static class NamePathModel implements Comparable<NamePathModel> { |
| |
| private final ExplorerBackend backend; |
| private final String name; |
| private final String path; |
| private final boolean skipSizeCheck; |
| |
| private boolean loaded = false; |
| |
| private Long[] size = {-1L, -1L}; |
| |
| NamePathModel(String name, String path, NodeState state, Map<String, Long[]> sizeCache, boolean skipSizeCheck, ExplorerBackend backend) { |
| this.backend = backend; |
| this.name = name; |
| this.path = path; |
| this.skipSizeCheck = skipSizeCheck; |
| if (!skipSizeCheck && backend.isPersisted(state)) { |
| this.size = exploreSize(state, sizeCache, backend); |
| } |
| } |
| |
| void loaded() { |
| loaded = true; |
| } |
| |
| boolean isLoaded() { |
| return loaded; |
| } |
| |
| @Override |
| public String toString() { |
| if (skipSizeCheck) { |
| return name; |
| } |
| if (size[1] > 0) { |
| return name + " (" + byteCountToDisplaySize(size[0]) + ";" |
| + byteCountToDisplaySize(size[1]) + ")"; |
| } |
| if (size[0] > 0) { |
| return name + " (" + byteCountToDisplaySize(size[0]) + ")"; |
| } |
| return name; |
| } |
| |
| public String getPath() { |
| return path; |
| } |
| |
| public NodeState getState() { |
| return loadState(); |
| } |
| |
| private NodeState loadState() { |
| NodeState n = backend.getHead(); |
| for (String p : elements(path)) { |
| n = n.getChildNode(p); |
| } |
| return n; |
| } |
| |
| @Override |
| public int compareTo(NamePathModel o) { |
| int s = size[0].compareTo(o.size[0]); |
| if (s != 0) { |
| return -1 * s; |
| } |
| s = size[1].compareTo(o.size[1]); |
| if (s != 0) { |
| return -1 * s; |
| } |
| if ("root".equals(name)) { |
| return 1; |
| } else if ("root".equals(o.name)) { |
| return -1; |
| } |
| return name.compareTo(o.name); |
| } |
| |
| public Long[] getSize() { |
| return size; |
| } |
| } |
| |
| private static Long[] exploreSize(NodeState ns, Map<String, Long[]> sizeCache, ExplorerBackend store) { |
| String key = store.getRecordId(ns); |
| if (sizeCache.containsKey(key)) { |
| return sizeCache.get(key); |
| } |
| Long[] s = {0L, 0L}; |
| |
| List<String> names = newArrayList(ns.getChildNodeNames()); |
| |
| if (names.contains("root")) { |
| List<String> temp = newArrayList(); |
| int poz = 0; |
| // push 'root' to the beginning |
| for (String n : names) { |
| if (n.equals("root")) { |
| temp.add(poz, n); |
| poz++; |
| } else { |
| temp.add(n); |
| } |
| } |
| names = temp; |
| } |
| |
| for (String n : names) { |
| NodeState k = ns.getChildNode(n); |
| String ckey = store.getRecordId(k); |
| if (sizeCache.containsKey(ckey)) { |
| // already been here, record size under 'link' |
| Long[] ks = sizeCache.get(ckey); |
| s[1] = s[1] + ks[0] + ks[1]; |
| } else { |
| Long[] ks = exploreSize(k, sizeCache, store); |
| s[0] = s[0] + ks[0]; |
| s[1] = s[1] + ks[1]; |
| } |
| } |
| for (PropertyState ps : ns.getProperties()) { |
| for (int j = 0; j < ps.count(); j++) { |
| if (ps.getType().tag() == Type.BINARY.tag()) { |
| Blob b = ps.getValue(Type.BINARY, j); |
| boolean skip = store.isExternal(b); |
| if (!skip) { |
| s[0] = s[0] + b.length(); |
| } |
| } else { |
| s[0] = s[0] + ps.size(j); |
| } |
| } |
| } |
| sizeCache.put(key, s); |
| return s; |
| } |
| |
| @Override |
| public void close() throws IOException { |
| backend.close(); |
| } |
| |
| } |