0N/A/*
0N/A * CDDL HEADER START
0N/A *
0N/A * The contents of this file are subject to the terms of the
0N/A * Common Development and Distribution License (the "License").
0N/A * You may not use this file except in compliance with the License.
0N/A *
0N/A * See LICENSE.txt included in this distribution for the specific
0N/A * language governing permissions and limitations under the License.
0N/A *
0N/A * When distributing Covered Code, include this CDDL HEADER in each
0N/A * file and include the License file at LICENSE.txt.
0N/A * If applicable, add the following below this CDDL HEADER, with the
0N/A * fields enclosed by brackets "[]" replaced with your own identifying
0N/A * information: Portions Copyright [yyyy] [name of copyright owner]
0N/A *
0N/A * CDDL HEADER END
0N/A */
0N/A
0N/A/*
1065N/A * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
0N/A */
1065N/A
0N/Apackage org.opensolaris.opengrok.search.context;
0N/A
392N/Aimport java.io.File;
1065N/Aimport java.io.IOException;
392N/Aimport java.io.Reader;
392N/Aimport java.io.Writer;
1068N/Aimport java.util.Collections;
829N/Aimport java.util.Iterator;
392N/Aimport java.util.List;
1138N/Aimport java.util.Map;
428N/Aimport java.util.logging.Level;
392N/Aimport org.apache.lucene.search.Query;
428N/Aimport org.opensolaris.opengrok.OpenGrokLogger;
829N/Aimport org.opensolaris.opengrok.history.History;
829N/Aimport org.opensolaris.opengrok.history.HistoryEntry;
615N/Aimport org.opensolaris.opengrok.history.HistoryException;
392N/Aimport org.opensolaris.opengrok.history.HistoryGuru;
0N/Aimport org.opensolaris.opengrok.search.Hit;
1113N/Aimport org.opensolaris.opengrok.web.Constants;
0N/A
0N/A/**
0N/A * it is supposed to get the matching lines from history log files.
0N/A * since lucene does not easily give the match context.
0N/A */
0N/Apublic class HistoryContext {
456N/A private final LineMatcher[] m;
0N/A HistoryLineTokenizer tokens;
1138N/A
1138N/A /**
1138N/A * Map whose keys tell which fields to look for in the history, and
1138N/A * whose values tell if the field is case insensitive (true for
1138N/A * insensitivity, false for sensitivity).
1138N/A */
1138N/A private static final Map<String, Boolean> tokenFields =
1185N/A Collections.singletonMap("hist", Boolean.TRUE);
1068N/A
0N/A public HistoryContext(Query query) {
0N/A QueryMatchers qm = new QueryMatchers();
0N/A m = qm.getMatchers(query, tokenFields);
0N/A if(m != null) {
0N/A tokens = new HistoryLineTokenizer((Reader)null);
0N/A }
0N/A }
0N/A public boolean isEmpty() {
0N/A return m == null;
0N/A }
615N/A
615N/A public boolean getContext(String filename, String path, List<Hit> hits)
615N/A throws HistoryException
615N/A {
0N/A if (m == null) {
0N/A return false;
0N/A }
0N/A File f = new File(filename);
829N/A return getHistoryContext(HistoryGuru.getInstance().getHistory(f),
1113N/A path, null, hits,null);
1190N/A
0N/A }
1190N/A
615N/A public boolean getContext(
1113N/A String parent, String basename, String path, Writer out, String context)
615N/A throws HistoryException
615N/A {
1185N/A return getContext(new File(parent, basename), path, out, context);
1185N/A }
1185N/A
1185N/A /**
1185N/A * Obtain the history for the source file <var>src</var> and write out
1185N/A * matching History log entries.
1190N/A *
1185N/A * @param src the source file represented by <var>path</var>
1185N/A * (SOURCE_ROOT + path)
1185N/A * @param path the path of the file (rooted at SOURCE_ROOT)
1185N/A * @param out write destination
1190N/A * @param context the servlet context path of the application (the path
1185N/A * prefix for URLs)
1185N/A * @return {@code true} if at least one line has been written out.
1185N/A * @throws HistoryException
1185N/A */
1185N/A public boolean getContext(File src, String path, Writer out, String context)
1185N/A throws HistoryException
1185N/A {
0N/A if (m == null) {
0N/A return false;
0N/A }
1185N/A History hist = HistoryGuru.getInstance().getHistory(src);
1113N/A return getHistoryContext(hist, path, out, null,context);
0N/A }
1185N/A
0N/A /**
1113N/A * Writes matching History log entries from 'in' to 'out' or to 'hits'
829N/A * @param in the history to fetch entries from
0N/A * @param out to write matched context
1113N/A * @param path path to the file
1113N/A * @param hits list of hits
1113N/A * @param wcontext web context - beginning of url
0N/A */
829N/A private boolean getHistoryContext(
1113N/A History in, String path, Writer out, List<Hit> hits, String wcontext) {
1065N/A if ((out == null) == (hits == null)) {
1065N/A // There should be exactly one destination for the output. If
1065N/A // none or both are specified, it's a bug.
1065N/A throw new IllegalArgumentException(
1065N/A "Exactly one of out and hits should be non-null");
1065N/A }
1065N/A
0N/A if (m == null) {
0N/A return false;
0N/A }
1065N/A
0N/A int matchedLines = 0;
829N/A Iterator<HistoryEntry> it = in.getHistoryEntries().iterator();
0N/A try {
1111N/A HistoryEntry he=null;
1111N/A HistoryEntry nhe=null;
1111N/A String nrev=null;
1111N/A while(( it.hasNext()||(nhe!=null) ) && matchedLines < 10) {
1112N/A if (nhe==null) { he=it.next(); }
1112N/A else { he=nhe; } //nhe is the lookahead revision
1111N/A String line = he.getLine();
1111N/A String rev = he.getRevision();
1111N/A if (it.hasNext()) { nhe=it.next(); } //this prefetch mechanism is here because of the diff link generation
1111N/A // we currently generate the diff to previous revision
1111N/A else {nhe=null;}
1112N/A if (nhe==null) { nrev=null; }
1112N/A else { nrev=nhe.getRevision(); }
1065N/A tokens.reInit(line);
0N/A String token;
1065N/A int matchState;
1065N/A int start = -1;
0N/A while ((token = tokens.next()) != null) {
0N/A for (int i = 0; i< m.length; i++) {
0N/A matchState = m[i].match(token);
0N/A if (matchState == LineMatcher.MATCHED) {
1065N/A if (start < 0) {
1065N/A start = tokens.getMatchStart();
1065N/A }
1065N/A int end = tokens.getMatchEnd();
1065N/A if (out == null) {
1113N/A StringBuilder sb = new StringBuilder();
1113N/A writeMatch(sb, line, start, end, true,path,wcontext,nrev,rev);
1113N/A hits.add(new Hit(path, sb.toString(), "", false, false));
1065N/A } else {
1113N/A writeMatch(out, line, start, end, false,path,wcontext,nrev,rev);
1065N/A }
0N/A matchedLines++;
0N/A break;
0N/A } else if (matchState == LineMatcher.WAIT) {
1065N/A if (start < 0) {
1065N/A start = tokens.getMatchStart();
1065N/A }
0N/A } else {
1065N/A start = -1;
0N/A }
0N/A }
0N/A }
0N/A }
0N/A } catch (Exception e) {
428N/A OpenGrokLogger.getLogger().log(Level.WARNING, "Could not get history context for " + path, e);
0N/A }
0N/A return matchedLines > 0;
0N/A }
1065N/A
1065N/A /**
1065N/A * Write a match to a stream.
1065N/A *
1065N/A * @param out the receiving stream
1065N/A * @param line the matching line
1065N/A * @param start start position of the match
1065N/A * @param end position of the first char after the match
1065N/A * @param flatten should multi-line log entries be flattened to a single
1113N/A * @param path path to the file
1113N/A * @param wcontext web context (begin of url)
1113N/A * @param nrev old revision
1113N/A * @param rev current revision
1065N/A * line? If {@code true}, replace newline with space.
1065N/A */
1065N/A private void writeMatch(Appendable out, String line,
1113N/A int start, int end, boolean flatten, String path, String wcontext, String nrev, String rev)
1065N/A throws IOException {
1065N/A String prefix = line.substring(0, start);
1065N/A String match = line.substring(start, end);
1065N/A String suffix = line.substring(end);
1065N/A
1113N/A if (wcontext!=null && nrev!=null && !wcontext.isEmpty() ) {
1113N/A //does below need to be encoded? see bug 16985
1116N/A out.append("<a href="+wcontext+Constants.diffP+path+"?r2="+path+"@"+rev+"&r1="+path+"@"+nrev+" title=\"diff to previous version\">diff</a> ");
1113N/A }
1113N/A
1065N/A printHTML(out, prefix, flatten);
1065N/A out.append("<b>");
1065N/A printHTML(out, match, flatten);
1065N/A out.append("</b>");
1065N/A printHTML(out, suffix, flatten);
1065N/A }
1065N/A
1065N/A /**
1065N/A * Output a string as HTML.
1065N/A *
1065N/A * @param out where to write the HTML
1065N/A * @param str the string to print
1065N/A * @param flatten should multi-line strings be flattened to a single
1065N/A * line? If {@code true}, replace newline with space.
1065N/A */
1065N/A private void printHTML(Appendable out, String str, boolean flatten)
1065N/A throws IOException {
1065N/A for (int i = 0; i < str.length(); i++) {
1065N/A char ch = str.charAt(i);
1065N/A switch (ch) {
1065N/A case '\n':
1065N/A out.append(flatten ? " " : "<br/>");
1065N/A break;
1065N/A case '<':
1065N/A out.append("&lt;");
1065N/A break;
1065N/A case '>':
1065N/A out.append("&gt;");
1065N/A break;
1065N/A case '&':
1065N/A out.append("&amp;");
1065N/A break;
1065N/A default:
1065N/A out.append(ch);
1065N/A }
1065N/A }
1065N/A }
0N/A}