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/*
1340N/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;
280N/Aimport org.apache.lucene.search.Query;
428N/Aimport org.opensolaris.opengrok.OpenGrokLogger;
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
890N/Apublic class Context {
0N/A
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
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 }
1185N/A sb.append(field).append("=").append(Util.URIEncode(queryText))
1185N/A .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 /**
1185N/A * ???.
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
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?
0N/A * @return Did it get any matching context?
0N/A */
350N/A public boolean getContext(Reader in, Writer out, String urlPrefix,
890N/A String morePrefix, String path, Definitions tags,
890N/A boolean limit, List<Hit> hits) {
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;
1340N/A String urlPrefixE =
1340N/A (urlPrefix == null) ? "" : Util.URIEncodePath(urlPrefix);
1185N/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) {
1185N/A /* desc[0] is matched symbol
350N/A * desc[1] is line number
350N/A * desc[2] is type
1185N/A * desc[3] is matching line;
350N/A */
350N/A String[] desc = {
350N/A tag.symbol,
350N/A Integer.toString(tag.line),
350N/A tag.type,
890N/A tag.text,};
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 {
350N/A out.write("<a class=\"s\" href=\"");
1185N/A out.write(urlPrefixE);
1185N/A out.write(pathE);
350N/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(
350N/A desc[0], "<b>" + desc[0] + "</b>"));
1185N/A out.write("</a> <i>");
350N/A out.write(desc[2]);
1185N/A out.write("</i><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?
428N/A OpenGrokLogger.getLogger().log(Level.WARNING, "Could not get context for " + path, 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;
280N/A if (!RuntimeEnvironment.getInstance().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) {
597N/A OpenGrokLogger.getLogger().log(Level.WARNING, "An error occured while reading data", e);
597N/A return anything;
597N/A }
597N/A if (charsRead == 0) {
597N/A return anything;
597N/A }
890N/A
1185N/A tokens.reInit(buffer, charsRead, out, urlPrefixE + pathE + "#", 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++;
597N/A //out.write("<br> <i>Matched " + token + " maxlines = " + 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) {
1185N/A out.write("<a href=\"" + Util.URIEncodePath(morePrefix) + pathE + "?" + queryAsURI + "\">[all...]</a>");
597N/A }
597N/A } catch (IOException e) {
597N/A OpenGrokLogger.getLogger().log(Level.WARNING, "Could not get context for " + path, e);
0N/A } finally {
1195N/A IOUtils.close(in);
1195N/A
508N/A if (out != null) {
508N/A try {
0N/A out.flush();
508N/A } catch (IOException e) {
1219N/A OpenGrokLogger.getLogger().log(Level.WARNING, "Failed to flush stream: ", e);
0N/A }
508N/A }
0N/A }
597N/A return anything;
0N/A }
0N/A}