xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/web/EftarFileReader.java (revision edf8e58e8be5a0bcb9281ff841102d55f59d002d)
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