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/*
1376N/A * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
1190N/A *
1185N/A * Portions Copyright 2011 Jens Elkner.
0N/A */
986N/A
0N/A/**
0N/A * This is supposed to get the matching lines from sourcefile.
0N/A * since lucene does not easily give the match context.
0N/A */
0N/Apackage org.opensolaris.opengrok.search.context;
0N/A
280N/Aimport java.io.IOException;
280N/Aimport java.io.Reader;
280N/Aimport java.io.Writer;
1138N/Aimport java.util.HashMap;
280N/Aimport java.util.List;
986N/Aimport java.util.Map;
280N/Aimport java.util.TreeMap;
428N/Aimport java.util.logging.Level;
1327N/Aimport java.util.logging.Logger;
1327N/A
280N/Aimport org.apache.lucene.search.Query;
350N/Aimport org.opensolaris.opengrok.analysis.Definitions;
106N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
0N/Aimport org.opensolaris.opengrok.search.Hit;
1195N/Aimport org.opensolaris.opengrok.util.IOUtils;
0N/Aimport org.opensolaris.opengrok.web.Util;
0N/A
1469N/A/**
1469N/A * Search Context.
1469N/A *
1469N/A * @author Trond Norbye
1469N/A * @version $Revision$
1469N/A */
890N/Apublic class Context {
0N/A
1327N/A private static final Logger logger = Logger.getLogger(Context.class.getName());
456N/A private final LineMatcher[] m;
890N/A static final int MAXFILEREAD = 1024 * 1024;
0N/A private char[] buffer;
0N/A PlainLineTokenizer tokens;
0N/A String queryAsURI;
890N/A
1138N/A /**
1138N/A * Map whose keys tell which fields to look for in the source file, 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 =
1138N/A new HashMap<String, Boolean>();
0N/A static {
1185N/A tokenFields.put("full", Boolean.TRUE);
1185N/A tokenFields.put("refs", Boolean.FALSE);
1185N/A tokenFields.put("defs", Boolean.FALSE);
0N/A }
890N/A
0N/A /**
0N/A * Constructs a context generator
890N/A * @param query the query to generate the result for
986N/A * @param queryStrings map from field names to queries against the fields
0N/A */
986N/A public Context(Query query, Map<String, String> queryStrings) {
0N/A QueryMatchers qm = new QueryMatchers();
342N/A m = qm.getMatchers(query, tokenFields);
890N/A if (m != null) {
986N/A buildQueryAsURI(queryStrings);
890N/A //System.err.println("Found Matchers = "+ m.length + " for " + query);
890N/A buffer = new char[MAXFILEREAD];
890N/A tokens = new PlainLineTokenizer((Reader) null);
0N/A }
0N/A }
890N/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 #Context(Query, Map)
1469N/A */
0N/A public boolean isEmpty() {
0N/A return m == null;
890N/A }
890N/A
986N/A /**
986N/A * Build the {@code queryAsURI} string that holds the query in a form
986N/A * that's suitable for sending it as part of a URI.
986N/A *
986N/A * @param subqueries a map containing the query text for each field
986N/A */
986N/A private void buildQueryAsURI(Map<String, String> subqueries) {
1185N/A if (subqueries.isEmpty()) {
1185N/A queryAsURI = "";
1185N/A return;
1185N/A }
986N/A StringBuilder sb = new StringBuilder();
986N/A for (Map.Entry<String, String> entry : subqueries.entrySet()) {
986N/A String field = entry.getKey();
986N/A String queryText = entry.getValue();
1236N/A if ("full".equals(field)) {
1185N/A field = "q"; // bah - search query params should be consistent!
986N/A }
1469N/A sb.append(field).append("=")
1469N/A .append(Util.uriEncodeQueryValue(queryText)).append('&');
986N/A }
1185N/A sb.setLength(sb.length()-1);
986N/A queryAsURI = sb.toString();
986N/A }
986N/A
890N/A private boolean alt = true;
890N/A
1071N/A /**
1469N/A * Search given tags in the given input stream and write out htmlized
1469N/A * results to the given output stream.
1185N/A * Closes the given <var>in</var> reader on return.
1190N/A *
0N/A * @param in File to be matched
0N/A * @param out to write the context
1469N/A * @param urlPrefix URL prefix to use for generated links. Ignored if
1469N/A * {@code null}.
0N/A * @param morePrefix to link to more... page
0N/A * @param path path of the file
0N/A * @param tags format to highlight defs.
0N/A * @param limit should the number of matching lines be limited?
1469N/A * @param hits where to add obtained hits
0N/A * @return Did it get any matching context?
0N/A */
1469N/A @SuppressWarnings("boxing")
350N/A public boolean getContext(Reader in, Writer out, String urlPrefix,
1469N/A String morePrefix, String path, Definitions tags, boolean limit,
1469N/A List<Hit> hits)
1469N/A {
0N/A alt = !alt;
0N/A if (m == null) {
1195N/A IOUtils.close(in);
0N/A return false;
0N/A }
0N/A boolean anything = false;
0N/A TreeMap<Integer, String[]> matchingTags = null;
1376N/A String urlPrefixE =
1469N/A (urlPrefix == null) ? "" : Util.uriEncodePath(urlPrefix);
1469N/A String pathE = Util.uriEncodePath(path);
0N/A if (tags != null) {
0N/A matchingTags = new TreeMap<Integer, String[]>();
0N/A try {
350N/A for (Definitions.Tag tag : tags.getTags()) {
350N/A for (int i = 0; i < m.length; i++) {
350N/A if (m[i].match(tag.symbol) == LineMatcher.MATCHED) {
350N/A String[] desc = {
1469N/A tag.symbol, // matched symbol
1469N/A Integer.toString(tag.line), // line number
1469N/A tag.type, // tag type
1469N/A tag.text // matching line
1469N/A };
460N/A if (in == null) {
460N/A if (out == null) {
460N/A Hit hit = new Hit(path,
1253N/A Util.htmlize(desc[3]).replace(
460N/A desc[0], "<b>" + desc[0] + "</b>"),
460N/A desc[1], false, alt);
460N/A hits.add(hit);
460N/A anything = true;
460N/A } else {
1390N/A out.write("<a class=\"rsh\" href=\"");
1185N/A out.write(urlPrefixE);
1185N/A out.write(pathE);
1469N/A out.write('#');
350N/A out.write(desc[1]);
350N/A out.write("\"><span class=\"l\">");
350N/A out.write(desc[1]);
350N/A out.write("</span> ");
1253N/A out.write(Util.htmlize(desc[3]).replace(
1470N/A desc[0], "<b>" + desc[0] + "</b>"));
1390N/A out.write("</a> <span class=\"rshd\">");
350N/A out.write(desc[2]);
1390N/A out.write("</span><br/>");
1071N/A anything = true;
0N/A }
460N/A } else {
460N/A matchingTags.put(tag.line, desc);
0N/A }
350N/A break;
0N/A }
0N/A }
0N/A }
1185N/A } catch (Exception e) {
0N/A if (hits != null) {
0N/A // @todo verify why we ignore all exceptions?
1469N/A logger.warning("Could not get context for '" + path + "': "
1327N/A + e.getMessage());
1327N/A logger.log(Level.FINE, "getContext", e);
0N/A }
0N/A }
0N/A }
0N/A /**
0N/A * Just to get the matching tag send a null in
890N/A */
428N/A if (in == null) {
0N/A return anything;
0N/A }
0N/A int charsRead = 0;
0N/A boolean truncated = false;
890N/A
439N/A boolean lim = limit;
1470N/A if (!RuntimeEnvironment.getConfig().isQuickContextScan()) {
439N/A lim = false;
280N/A }
890N/A
597N/A if (lim) {
890N/A try {
597N/A charsRead = in.read(buffer);
597N/A if (charsRead == MAXFILEREAD) {
597N/A // we probably only read parts of the file, so set the
597N/A // truncated flag to enable the [all...] link that
597N/A // requests all matches
597N/A truncated = true;
597N/A // truncate to last line read (don't look more than 100
597N/A // characters back)
890N/A for (int i = charsRead - 1; i > charsRead - 100; i--) {
597N/A if (buffer[i] == '\n') {
597N/A charsRead = i;
597N/A break;
0N/A }
0N/A }
0N/A }
597N/A } catch (IOException e) {
1327N/A logger.warning("An error occured while reading data: " + e.getMessage());
1327N/A logger.log(Level.FINE, "getContext", e);
597N/A return anything;
597N/A }
597N/A if (charsRead == 0) {
597N/A return anything;
597N/A }
890N/A
1469N/A tokens.reInit(buffer, charsRead, out, urlPrefixE + pathE + '#',
1469N/A matchingTags);
597N/A } else {
1185N/A tokens.reInit(in, out, urlPrefixE + pathE + "#", matchingTags);
597N/A }
890N/A
597N/A if (hits != null) {
597N/A tokens.setAlt(alt);
597N/A tokens.setHitList(hits);
597N/A tokens.setFilename(path);
597N/A }
890N/A
597N/A try {
597N/A String token;
597N/A int matchState = LineMatcher.NOT_MATCHED;
597N/A int matchedLines = 0;
821N/A while ((token = tokens.yylex()) != null && (!lim || matchedLines < 10)) {
890N/A for (int i = 0; i < m.length; i++) {
597N/A matchState = m[i].match(token);
597N/A if (matchState == LineMatcher.MATCHED) {
597N/A tokens.printContext();
597N/A matchedLines++;
1469N/A //out.write("<br> <i>Matched " + token + " maxlines = "
1469N/A // + matchedLines + "</i><br>");
597N/A break;
890N/A } else if (matchState == LineMatcher.WAIT) {
597N/A tokens.holdOn();
597N/A } else {
597N/A tokens.neverMind();
597N/A }
596N/A }
0N/A }
597N/A anything = matchedLines > 0;
597N/A tokens.dumpRest();
597N/A if (lim && (truncated || matchedLines == 10) && out != null) {
1470N/A out.write("<a href=\"" + morePrefix + pathE
1469N/A + '?' + queryAsURI + "\">[all...]</a>");
597N/A }
597N/A } catch (IOException e) {
1327N/A logger.warning("Could not get context for '" + path + "': "
1327N/A + e.getMessage());
1327N/A logger.log(Level.FINE, "getContext", e);
0N/A } finally {
1195N/A IOUtils.close(in);
1327N/A // don't close
508N/A if (out != null) {
508N/A try {
0N/A out.flush();
508N/A } catch (IOException e) {
1327N/A logger.warning("Failed to flush stream: " + e.getMessage());
1327N/A logger.log(Level.FINE, "getContext", e);
0N/A }
508N/A }
0N/A }
597N/A return anything;
0N/A }
0N/A}