1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * See LICENSE.txt included in this distribution for the specific 9 * language governing permissions and limitations under the License. 10 * 11 * When distributing Covered Code, include this CDDL HEADER in each 12 * file and include the License file at LICENSE.txt. 13 * If applicable, add the following below this CDDL HEADER, with the 14 * fields enclosed by brackets "[]" replaced with your own identifying 15 * information: Portions Copyright [yyyy] [name of copyright owner] 16 * 17 * CDDL HEADER END 18 */ 19 20 /* 21 * Copyright (c) 2005, 2021, Oracle and/or its affiliates. All rights reserved. 22 */ 23 package org.opengrok.indexer.web; 24 25 import java.io.Closeable; 26 import java.io.EOFException; 27 import java.io.File; 28 import java.io.FileNotFoundException; 29 import java.io.IOException; 30 import java.io.RandomAccessFile; 31 import java.util.StringTokenizer; 32 import java.util.logging.Level; 33 import java.util.logging.Logger; 34 35 import org.opengrok.indexer.logger.LoggerFactory; 36 import org.opengrok.indexer.util.IOUtils; 37 38 /** 39 * An Extremely Fast Tagged Attribute Read-only File Reader. 40 * Created on October 12, 2005 41 * 42 * @author Chandan 43 */ 44 public class EftarFileReader implements Closeable { 45 46 private static final Logger LOGGER = LoggerFactory.getLogger(EftarFileReader.class); 47 48 private final RandomAccessFile f; 49 private boolean isOpen; 50 51 public class FNode { 52 53 private final long offset; 54 private long hash; 55 private int childOffset; 56 private int numChildren; 57 private int tagOffset; 58 FNode()59 public FNode() throws IOException { 60 offset = f.getFilePointer(); 61 62 try { 63 hash = f.readLong(); 64 childOffset = f.readUnsignedShort(); 65 numChildren = f.readUnsignedShort(); 66 tagOffset = f.readUnsignedShort(); 67 } catch (EOFException e) { 68 numChildren = 0; 69 tagOffset = 0; 70 } 71 } 72 FNode(long hash, long offset, int childOffset, int num, int tagOffset)73 public FNode(long hash, long offset, int childOffset, int num, int tagOffset) { 74 this.hash = hash; 75 this.offset = offset; 76 this.childOffset = childOffset; 77 this.numChildren = num; 78 this.tagOffset = tagOffset; 79 } 80 get(long hash)81 public FNode get(long hash) throws IOException { 82 if (childOffset == 0 || numChildren == 0) { 83 return null; 84 } 85 return binarySearch(offset + childOffset, numChildren, hash); 86 } 87 binarySearch(long start, int len, long hash)88 private FNode binarySearch(long start, int len, long hash) throws IOException { 89 int b = 0; 90 int e = len; 91 while (b <= e) { 92 int m = (b + e) / 2; 93 f.seek(start + (long) m * EftarFile.RECORD_LENGTH); 94 long mhash = f.readLong(); 95 if (hash > mhash) { 96 b = m + 1; 97 } else if (hash < mhash) { 98 e = m - 1; 99 } else { 100 return new FNode(mhash, f.getFilePointer() - 8L, f.readUnsignedShort(), f.readUnsignedShort(), 101 f.readUnsignedShort()); 102 } 103 } 104 return null; 105 } 106 getTag()107 public String getTag() throws IOException { 108 if (tagOffset == 0) { 109 return null; 110 } 111 f.seek(offset + tagOffset); 112 byte[] tagString; 113 if (childOffset == 0) { 114 tagString = new byte[numChildren]; 115 } else { 116 tagString = new byte[childOffset - tagOffset]; 117 } 118 int len = f.read(tagString); 119 if (len == -1) { 120 throw new EOFException(); 121 } 122 return new String(tagString, 0, len); 123 } 124 125 @Override toString()126 public String toString() { 127 String tagString = null; 128 try { 129 tagString = getTag(); 130 } catch (EOFException e) { // NOPMD 131 // ignore 132 } catch (IOException e) { 133 LOGGER.log(Level.WARNING, "Got exception while getting the tag: ", e); 134 } 135 return "H[" + hash + "] num = " + numChildren + " tag = " + tagString; 136 } 137 getChildOffset()138 public int getChildOffset() { 139 return childOffset; 140 } 141 } 142 EftarFileReader(String file)143 public EftarFileReader(String file) throws FileNotFoundException { 144 this(new File(file)); 145 } 146 EftarFileReader(File file)147 public EftarFileReader(File file) throws FileNotFoundException { 148 f = new RandomAccessFile(file, "r"); 149 isOpen = true; 150 } 151 getNode(String path)152 public FNode getNode(String path) throws IOException { 153 StringTokenizer toks = new StringTokenizer(path, "/"); 154 f.seek(0); 155 FNode n = new FNode(); 156 if (File.separator.equals(path) || path.length() == 0) { 157 return n; 158 } 159 FNode next = null; 160 while (toks.hasMoreTokens() && ((next = n.get(EftarFile.myHash(toks.nextToken()))) != null)) { 161 n = next; 162 } 163 if (!toks.hasMoreElements()) { 164 return next; 165 } 166 return null; 167 } 168 getChildTag(FNode fn, String name)169 public String getChildTag(FNode fn, String name) throws IOException { 170 if (fn != null && fn.childOffset != 0 && fn.numChildren != 0) { 171 FNode ch = fn.binarySearch(fn.offset + fn.childOffset, fn.numChildren, EftarFile.myHash(name)); 172 if (ch != null) { 173 return ch.getTag(); 174 } 175 } 176 return null; 177 } 178 179 /** 180 * Get description for path. 181 * @param path path relative to source root 182 * @return path description string 183 * @throws IOException I/O 184 */ get(String path)185 public String get(String path) throws IOException { 186 StringTokenizer toks = new StringTokenizer(path, "/"); 187 f.seek(0); 188 FNode n = new FNode(); 189 FNode next; 190 long tagOffset = 0; 191 int tagLength = 0; 192 while (toks.hasMoreTokens()) { 193 String tok = toks.nextToken(); 194 if (tok == null || tok.length() == 0) { 195 continue; 196 } 197 next = n.get(EftarFile.myHash(tok)); 198 if (next == null) { 199 break; 200 } 201 if (next.tagOffset != 0) { 202 tagOffset = next.offset + next.tagOffset; 203 if (next.childOffset == 0) { 204 tagLength = next.numChildren; 205 } else { 206 tagLength = next.childOffset - next.tagOffset; 207 } 208 } 209 n = next; 210 } 211 if (tagOffset != 0) { 212 f.seek(tagOffset); 213 byte[] desc = new byte[tagLength]; 214 int len = f.read(desc); 215 if (len == -1) { 216 throw new EOFException(); 217 } 218 return new String(desc, 0, len); 219 } 220 return ""; 221 } 222 223 /** 224 * Check, whether this instance has been already closed. 225 * @return {@code true} if closed. 226 */ isClosed()227 public boolean isClosed() { 228 return !isOpen; 229 } 230 231 @Override close()232 public void close() { 233 if (isOpen) { 234 IOUtils.close(f); 235 isOpen = false; 236 } 237 } 238 } 239