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;
1327N/Aimport java.util.logging.Logger;
1327N/A
392N/Aimport org.apache.lucene.search.Query;
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;
1419N/Aimport org.opensolaris.opengrok.web.Prefix;
1469N/Aimport org.opensolaris.opengrok.web.Util;
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 {
1469N/A private static final Logger logger =
1469N/A Logger.getLogger(HistoryContext.class.getName());
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 =
1469N/A Collections.singletonMap("hist", Boolean.TRUE);
1068N/A
1469N/A /**
1469N/A * Create a new context wrt. to the given query.
1469N/A * @param query query to use.
1469N/A */
0N/A public HistoryContext(Query query) {
0N/A QueryMatchers qm = new QueryMatchers();
0N/A m = qm.getMatchers(query, tokenFields);
1469N/A if (m != null) {
0N/A tokens = new HistoryLineTokenizer((Reader)null);
0N/A }
0N/A }
1469N/A
1469N/A /**
1469N/A * Check, whether one ore more line matchers (query terms) are present.
1469N/A * @return {@code true} if present.
1469N/A * @see #HistoryContext(Query)
1469N/A */
0N/A public boolean isEmpty() {
0N/A return m == null;
0N/A }
615N/A
1469N/A /**
1469N/A * Obtain the history for the source file <var>filename</var> and append
1469N/A * matches to <var>hits</var>.
1469N/A *
1469N/A * @param filename the source file for which the history should be fetched
1469N/A * @param path the path of the source file (rooted at SOURCE_ROOT)
1469N/A * @param hits where to append matches.
1469N/A * @return {@code true} if matches where found.
1469N/A * @throws HistoryException
1469N/A */
615N/A public boolean getContext(String filename, String path, List<Hit> hits)
1469N/A throws HistoryException
615N/A {
0N/A if (m == null) {
0N/A return false;
0N/A }
0N/A File f = new File(filename);
1469N/A return getHistoryContext(HistoryGuru.getInstance().getHistory(f), path,
1469N/A null, hits, null);
0N/A }
1190N/A
1469N/A /**
1469N/A * Obtain the history for the source file <var>parent/filename</var> and
1469N/A * write out matching history log entries htmlized.
1469N/A *
1469N/A * @param parent the directory which contains the source file
1469N/A * @param basename the basename of the source file
1469N/A * @param path the path of the source file (rooted at SOURCE_ROOT)
1469N/A * @param out where to write htmlized results
1469N/A * @param context the servlet context path of the application (the path
1469N/A * prefix for URLs)
1469N/A * @return {@code true} if matches where found and written out.
1469N/A * @throws HistoryException
1469N/A */
1469N/A public boolean getContext(String parent, String basename, String path,
1469N/A Writer out, String context) 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
1469N/A * matching history log entries htmlized.
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 /**
1469N/A * Writes matching history log entries from 'in' either htmlized to 'out'
1469N/A * or append them 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 */
1469N/A @SuppressWarnings("null")
1469N/A private boolean getHistoryContext(History in, String path, Writer out,
1469N/A List<Hit> hits, String wcontext)
1469N/A {
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(
1469N/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 {
1469N/A HistoryEntry he = null;
1469N/A HistoryEntry nhe = null; // next HE, the lookahead revision
1469N/A String nrev = null;
1469N/A while((it.hasNext() || (nhe != null)) && matchedLines < 10) {
1469N/A he = nhe == null ? it.next() : nhe;
1111N/A String line = he.getLine();
1111N/A String rev = he.getRevision();
1469N/A nhe = it.hasNext()
1469N/A // this prefetch mechanism is here because of the diff link
1469N/A // generation
1469N/A ? it.next()
1469N/A // we currently generate the diff to previous revision
1469N/A : null;
1469N/A nrev = nhe == null ? null : 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();
1469N/A writeMatch(sb, line, start, end, true, path,
1469N/A wcontext, nrev, rev);
1113N/A hits.add(new Hit(path, sb.toString(), "", false, false));
1065N/A } else {
1469N/A writeMatch(out, line, start, end, false, path,
1469N/A 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) {
1469N/A logger.warning("Could not get history context for '" + path + "': "
1327N/A + e.getMessage());
0N/A }
0N/A return matchedLines > 0;
0N/A }
1065N/A
1065N/A /**
1469N/A * Write a match htmlized 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
1469N/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 */
1469N/A private static void writeMatch(Appendable out, String line, int start,
1469N/A int end, boolean flatten, String path, String wcontext, String nrev,
1469N/A String rev) throws IOException
1469N/A {
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
1469N/A if (wcontext != null && nrev != null && !wcontext.isEmpty()) {
1113N/A //does below need to be encoded? see bug 16985
1469N/A String qv = Util.uriEncodeQueryValue(path);
1469N/A out.append("<a href=\"").append(wcontext)
1469N/A .append(Prefix.DIFF_P.toString()).append(Util.uriEncodePath(path))
1469N/A .append("?r2=").append(qv).append('@').append(rev)
1469N/A .append("&amp;r1=").append(qv).append('@').append(nrev)
1469N/A .append("\" title=\"diff to previous version\">diff</a> ");
1113N/A }
1469N/A String eol = flatten ? " " : "<br/>";
1469N/A out.append(Util.htmlize(prefix, eol)).append("<b>")
1469N/A .append(Util.htmlize(match, eol)).append("</b>")
1469N/A .append(Util.htmlize(suffix, eol));
1065N/A }
0N/A}