/*
* This file is part of ReqTracker.
*
* Copyright (C) 2015 Taleh Didover, Florian Gerdes, Dmitry Gorelenkov,
* Rajab Hassan Kaoneka, Katsiaryna Krauchanka, Tobias Polzer,
* Gayathery Sathya, Lukas Tajak
*
* ReqTracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ReqTracker is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with ReqTracker. If not, see <http://www.gnu.org/licenses/>.
*/
package de.fau.osr.util;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.util.IntList;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
/**
* FlatSource is a RawText that has been broken into a single line per word.
* @author tobias
*/
public class FlatSource extends RawText {
/**
* Maps (zero based) line numbers in unbroken file to start bytes
*/
final public IntList realLines;
/**
* Breaks a string at every word boundary to enable fine grained diffs
* @param source
*/
private FlatSource(byte[] input, IntList realLines) {
//binary files are useless here, so just set them empty
super(input);
this.realLines = realLines;
}
public static FlatSource flatten(byte[] input) {
// file is binary, ignore all contents
if(RawText.isBinary(input)) return new FlatSource(new byte[0], new IntList());
IntList realLines = new IntList();
ByteBuffer to = ByteBuffer.allocate(input.length*2), from = ByteBuffer.wrap(input);
boolean atLineStart = true, lastWord = false, lastSpace = false;
while(from.hasRemaining()) {
if(atLineStart) {
realLines.add(to.position());
}
byte cur = from.get();
if (cur == '\n') {
atLineStart = true;
to.put(cur);
} else if (cur == ' ' || cur == '\t' ) {
if(!atLineStart && !lastSpace)
to.put((byte)'\n');
to.put(cur);
lastSpace = true;
lastWord = false;
atLineStart = false;
} else {
if(!atLineStart && !lastWord)
to.put((byte)'\n');
to.put(cur);
lastSpace = false;
lastWord = true;
atLineStart = false;
}
}
byte[] out = new byte[to.position()];
to.position(0);
to.get(out);
return new FlatSource(out, realLines);//new FlatSource(new byte[])
}
/**
* Returns the line number a given (zero indexed) byte is part of
* @param byteIndex
* @return
*/
public int getLineByByte(int byteIndex) {
int low = 0, high = realLines.size();
while(high - low > 1) {
int mid = (high + low)/2;
if(realLines.get(mid) > byteIndex) high = mid;
else low = mid;
}
return low;
}
/**
* Returns the byte index into this FlatSource of the word at rank wordIndex
* @param wordIndex
* @return
*/
public int getByte(int wordIndex) {
return lines.get(wordIndex + 1);
}
/**
* returns the real line number of a given word
* @param wordIndex
* @return
*/
public int getLineByWord(int wordIndex) {
return getLineByByte(getByte(wordIndex));
}
/**
* reassemble one whole line and parse it into a string
* @param line zero based line index
* @return
*/
public String getLine(int line) {
int offset = realLines.get(line);
int length;
if(realLines.size() > line + 1)
length = realLines.get(line+1) - offset;
else
length = content.length - offset;
return new String(content, offset, length).replace("\n","");
}
public String toString() {
StringBuilder sb = new StringBuilder();
for(int i=0; i<realLines.size(); ++i) {
sb.append(getLine(i) + "\n");
}
return sb.toString();
}
private static class ObjectKey {
public ObjectKey(Function<ObjectId, byte[]> fun, ObjectId id) {
this.fun = fun;
this.id = id;
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ObjectKey other = (ObjectKey) obj;
if(fun != other.fun) return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
final Function<ObjectId, byte[]> fun;
final ObjectId id;
}
static private LoadingCache<ObjectKey, FlatSource> flatCache = CacheBuilder.newBuilder()
.maximumSize(10)
.build(new CacheLoader<ObjectKey, FlatSource>() {
@Override
public FlatSource load(ObjectKey key) throws Exception {
return flatten(key.fun.apply(key.id));
}
});
/**
* Either calls flatten(input.apply(idAfter)) or returns a cached FlatSource.
* For the caching mechanism to be consistent, idAfter and the address of
* input have to uniquely identify the contents of the file (trivial for git).
* @param input
* @param idAfter
* @return
*/
public static FlatSource flatten(Function<ObjectId, byte[]> input, ObjectId idAfter) {
try {
return flatCache.get(new ObjectKey(input, idAfter));
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}