<commit_before>// $Header$
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
*/

package org.apache.jmeter.visualizers;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.UnsupportedEncodingException;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeSelectionModel;

import org.apache.jmeter.samplers.Clearable;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

/**
 * Allows the tester to view the textual response from sampling an Entry. This
 * also allows to "single step through" the sampling process via a nice
 * "Continue" button.
 *
 * @author    <NAME>
 * Created     2001/07/25
 * @version   $Revision$ $Date$
 */
public class ViewResultsFullVisualizer
    extends AbstractVisualizer
    implements ActionListener, TreeSelectionListener, Clearable
{
    transient private static Logger log = LoggingManager.getLoggerForClass();

    public final static Color SERVER_ERROR_COLOR = Color.red;
    public final static Color CLIENT_ERROR_COLOR = Color.blue;
    public final static Color REDIRECT_COLOR = Color.green;

    private static final String HTML_BUTTON_LABEL = "Render HTML";
    private static final String TEXT_BUTTON_LABEL = "Show Text";

    private static final String HTML_COMMAND = "html";
    private static final String TEXT_COMMAND = "text";
    private boolean textMode = true;

    private DefaultMutableTreeNode root;
    private DefaultTreeModel treeModel;

    private JTextPane stats;
    private JEditorPane results;
    private JScrollPane resultsScrollPane;
    private JLabel imageLabel;
    private JTextArea sampleDataField;

    private JRadioButton textButton;
    private JRadioButton htmlButton;

    private JTree jTree;

    public ViewResultsFullVisualizer()
    {
        super();
        log.debug("Start : ViewResultsFullVisualizer1");
        init();
        log.debug("End : ViewResultsFullVisualizer1");
    }

    public void add(SampleResult res)
    {
        updateGui(res);
    }

    public String getStaticLabel()
    {
        return JMeterUtils.getResString("view_results_tree_title");
    }

    /**
     * Update the visualizer with new data.
     */
    public synchronized void updateGui(SampleResult res)
    {
        log.debug("Start : updateGui1");
        if (log.isDebugEnabled())
        {
            log.debug("updateGui1 : sample result - " + res);
        }
        DefaultMutableTreeNode currNode = new DefaultMutableTreeNode(res);

        treeModel.insertNodeInto(currNode, root, root.getChildCount());
        addSubResults(currNode, res);
        log.debug("End : updateGui1");
    }

    private void addSubResults(
        DefaultMutableTreeNode currNode,
        SampleResult res)
    {
        SampleResult[] subResults = res.getSubResults();

        if (subResults != null)
        {
            int leafIndex = 0;

            for (int i = 0; i < subResults.length; i++)
            {
                SampleResult child = subResults[i];

                if (log.isDebugEnabled())
                {
                    log.debug("updateGui1 : child sample result - " + child);
                }
                DefaultMutableTreeNode leafNode =
                    new DefaultMutableTreeNode(child);

                treeModel.insertNodeInto(leafNode, currNode, leafIndex++);
                addSubResults(leafNode, child);
            }
        }
    }

    /**
     * Clears the visualizer.
     */
    public void clear()
    {
        log.debug("Start : clear1");
        int totalChild = root.getChildCount();

        if (log.isDebugEnabled())
        {
            log.debug("clear1 : total child - " + totalChild);
        }
        for (int i = 0; i < totalChild; i++)
        {
            // the child to be removed will always be 0 'cos as the nodes are
            // removed the nth node will become (n-1)th
            treeModel.removeNodeFromParent(
                (DefaultMutableTreeNode) root.getChildAt(0));
        }

        results.setText("");//Response Data
        sampleDataField.setText("");//Request Data
        log.debug("End : clear1");
    }

    /**
     * Returns the description of this visualizer.
     *
     * @return   description of this visualizer
     */
    public String toString()
    {
        String desc = "Shows the text results of sampling in tree form";

        if (log.isDebugEnabled())
        {
            log.debug("toString1 : Returning description - " + desc);
        }
        return desc;
    }

    /**
     * Sets the right pane to correspond to the selected node of the left tree.
     */
    public void valueChanged(TreeSelectionEvent e)
    {
        log.debug("Start : valueChanged1");
        DefaultMutableTreeNode node =
            (DefaultMutableTreeNode) jTree.getLastSelectedPathComponent();

        if (log.isDebugEnabled())
        {
            log.debug("valueChanged : selected node - " + node);
        }

        StyledDocument statsDoc = stats.getStyledDocument();
        try
        {
            statsDoc.remove(0, statsDoc.getLength());
			sampleDataField.setText("");
			results.setText("");
            if (node != null)
            {
                SampleResult res = (SampleResult) node.getUserObject();

                if (log.isDebugEnabled())
                {
                    log.debug("valueChanged1 : sample result - " + res);
                }

                if (res != null)
                {
                    // load time label

                    log.debug("valueChanged1 : load time - " + res.getTime());
                    if (res != null && res.getSamplerData() != null)
                    {
                    	String sd;
                    	String rh = res.getRequestHeaders();
                    	if (rh==null)
                    	{
                    		sd=res.getSamplerData().trim();
                    	} else {
							sd=res.getSamplerData().trim()
							   +"\n"+rh;
                    	} 
                        sampleDataField.setText(sd);
                    }

                    statsDoc.insertString(
                        statsDoc.getLength(),
                        "Load time: " + res.getTime() + "\n",
                        null);

                    String responseCode = res.getResponseCode();
                    log.debug(
                        "valueChanged1 : response code - " + responseCode);

                    int responseLevel = 0;
                    if (responseCode != null)
                    {
                        try
                        {
                            responseLevel =
                                Integer.parseInt(responseCode) / 100;
                        }
                        catch (NumberFormatException numberFormatException)
                        {
                            // no need to change the foreground color
                        }
                    }

                    Style style = null;
                    switch (responseLevel)
                    {
                        case 3 :
                            style = statsDoc.getStyle("Redirect");
                            break;
                        case 4 :
                            style = statsDoc.getStyle("ClientError");
                            break;
                        case 5 :
                            style = statsDoc.getStyle("ServerError");
                            break;
                    }
                    statsDoc.insertString(
                        statsDoc.getLength(),
                        "HTTP response code: " + responseCode + "\n",
                        style);

                    // response message label
                    String responseMsgStr = res.getResponseMessage();

                    log.debug(
                        "valueChanged1 : response message - " + responseMsgStr);
                    statsDoc.insertString(
                        statsDoc.getLength(),
                        "HTTP response message: " + responseMsgStr + "\n",
                        null);

					statsDoc.insertString(
						statsDoc.getLength(),
						"\nHTTP response headers:\n" + res.getResponseHeaders() + "\n",
						null);

                    // get the text response and image icon
                    // to determine which is NOT null
                    byte[] responseBytes = (byte[]) res.getResponseData();

                    if (res.getDataType() != null
                        && res.getDataType().equals(SampleResult.TEXT))
                    {
                        String response = null;
                        try
                        {
							// Showing large strings can be VERY costly, so we will avoid doing so if the response
							// data is larger than 200K. TODO: instead, we could delay doing the result.setText
							// call until the user chooses the "Response data" tab. Plus we could warn the user
							// if this happens and revert the choice if he doesn't confirm he's ready to wait.
							if (responseBytes.length > 200*1024)
							{
								response= 
									("Response too large to be displayed ("+responseBytes.length+" bytes).");
							}
							else
							{
								response = new String(responseBytes, "UTF-8");
							}
                        }
                        catch (UnsupportedEncodingException err)
                        {
                            throw new Error(err.toString()); // UTF-8 not supported? Com'on!
                        }

                        if (textMode)
                        {
                            showTextResponse(response);
                        }
                        else
                        {
                            showRenderedResponse(response);
                        }
                    }
                    else if (responseBytes != null)
                    {
                        showImage(new ImageIcon(responseBytes));
                    }
                }
            }
        }
        catch (BadLocationException exc)
        {
            log.error("Error setting statistics text", exc);
            stats.setText("");
        }
        log.debug("End : valueChanged1");
    }

    private void showImage(Icon image)
    {
        imageLabel.setIcon(image);
        resultsScrollPane.setViewportView(imageLabel);
        textButton.setEnabled(false);
        htmlButton.setEnabled(false);
    }

    protected void showTextResponse(String response)
    {
        results.setContentType("text/plain");
		results.setText(response == null ? "" : response);
        results.setCaretPosition(0);
        resultsScrollPane.setViewportView(results);

        textButton.setEnabled(true);
        htmlButton.setEnabled(true);
    }

    /**
     * Display the response as text or as rendered HTML.  Change the
     * text on the button appropriate to the current display.
     * 
     * @param e the ActionEvent being processed
     */
    public void actionPerformed(ActionEvent e)
    {
        String command = e.getActionCommand();

        if (command != null
            && (
                command.equals(TEXT_COMMAND)
                ||
                command.equals(HTML_COMMAND)
               )
            )
        {

            textMode = command.equals(TEXT_COMMAND);

            DefaultMutableTreeNode node =
                (DefaultMutableTreeNode) jTree.getLastSelectedPathComponent();

            if (node == null)
            {
                results.setText("");
                return;
            }

            SampleResult res = (SampleResult) node.getUserObject();
            byte[] responseBytes = (byte[]) res.getResponseData();
            String response = null;

            if (res.getDataType() != null
                && res.getDataType().equals(SampleResult.TEXT))
            {
                try
                {
					// Showing large strings can be VERY costly, so we will avoid doing so if the response
					// data is larger than 200K. TODO: instead, we could delay doing the result.setText
					// call until the user chooses the "Response data" tab. Plus we could warn the user
					// if this happens and revert the choice if he doesn't confirm he's ready to wait.
					if (responseBytes.length > 200*1024)
					{
						response= 
							("Response too large to be displayed ("+responseBytes.length+" bytes).");
						log.warn("Response too large to display.");
					}
					else
					{
						response = new String(responseBytes, "UTF-8");
					}
                }
                catch (UnsupportedEncodingException err)
                {
                	throw new Error(err.toString()); // UTF-8 unsupported? Com'on!
                }
            }

            if (textMode)
            {
                showTextResponse(response);
            }<commit_msg>Factor out common code to convert result to string; use actual sample encoding instead of utf-8 use default encoding if all else fails<commit_after>// $Header$
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
*/

package org.apache.jmeter.visualizers;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.UnsupportedEncodingException;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeSelectionModel;

import org.apache.jmeter.samplers.Clearable;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;

/**
 * Allows the tester to view the textual response from sampling an Entry. This
 * also allows to "single step through" the sampling process via a nice
 * "Continue" button.
 *
 * @author    <NAME>
 * Created     2001/07/25
 * @version   $Revision$ $Date$
 */
public class ViewResultsFullVisualizer
    extends AbstractVisualizer
    implements ActionListener, TreeSelectionListener, Clearable
{
    transient private static Logger log = LoggingManager.getLoggerForClass();

    public final static Color SERVER_ERROR_COLOR = Color.red;
    public final static Color CLIENT_ERROR_COLOR = Color.blue;
    public final static Color REDIRECT_COLOR = Color.green;

    private static final String HTML_BUTTON_LABEL = "Render HTML";
    private static final String TEXT_BUTTON_LABEL = "Show Text";

    private static final String HTML_COMMAND = "html";
    private static final String TEXT_COMMAND = "text";
    private boolean textMode = true;

    private DefaultMutableTreeNode root;
    private DefaultTreeModel treeModel;

    private JTextPane stats;
    private JEditorPane results;
    private JScrollPane resultsScrollPane;
    private JLabel imageLabel;
    private JTextArea sampleDataField;

    private JRadioButton textButton;
    private JRadioButton htmlButton;

    private JTree jTree;

    public ViewResultsFullVisualizer()
    {
        super();
        log.debug("Start : ViewResultsFullVisualizer1");
        init();
        log.debug("End : ViewResultsFullVisualizer1");
    }

    public void add(SampleResult res)
    {
        updateGui(res);
    }

    public String getStaticLabel()
    {
        return JMeterUtils.getResString("view_results_tree_title");
    }

    /**
     * Update the visualizer with new data.
     */
    public synchronized void updateGui(SampleResult res)
    {
        log.debug("Start : updateGui1");
        if (log.isDebugEnabled())
        {
            log.debug("updateGui1 : sample result - " + res);
        }
        DefaultMutableTreeNode currNode = new DefaultMutableTreeNode(res);

        treeModel.insertNodeInto(currNode, root, root.getChildCount());
        addSubResults(currNode, res);
        log.debug("End : updateGui1");
    }

    private void addSubResults(
        DefaultMutableTreeNode currNode,
        SampleResult res)
    {
        SampleResult[] subResults = res.getSubResults();

        if (subResults != null)
        {
            int leafIndex = 0;

            for (int i = 0; i < subResults.length; i++)
            {
                SampleResult child = subResults[i];

                if (log.isDebugEnabled())
                {
                    log.debug("updateGui1 : child sample result - " + child);
                }
                DefaultMutableTreeNode leafNode =
                    new DefaultMutableTreeNode(child);

                treeModel.insertNodeInto(leafNode, currNode, leafIndex++);
                addSubResults(leafNode, child);
            }
        }
    }

    /**
     * Clears the visualizer.
     */
    public void clear()
    {
        log.debug("Start : clear1");
        int totalChild = root.getChildCount();

        if (log.isDebugEnabled())
        {
            log.debug("clear1 : total child - " + totalChild);
        }
        for (int i = 0; i < totalChild; i++)
        {
            // the child to be removed will always be 0 'cos as the nodes are
            // removed the nth node will become (n-1)th
            treeModel.removeNodeFromParent(
                (DefaultMutableTreeNode) root.getChildAt(0));
        }

        results.setText("");//Response Data
        sampleDataField.setText("");//Request Data
        log.debug("End : clear1");
    }

    /**
     * Returns the description of this visualizer.
     *
     * @return   description of this visualizer
     */
    public String toString()
    {
        String desc = "Shows the text results of sampling in tree form";

        if (log.isDebugEnabled())
        {
            log.debug("toString1 : Returning description - " + desc);
        }
        return desc;
    }

    /**
     * Sets the right pane to correspond to the selected node of the left tree.
     */
    public void valueChanged(TreeSelectionEvent e)
    {
        log.debug("Start : valueChanged1");
        DefaultMutableTreeNode node =
            (DefaultMutableTreeNode) jTree.getLastSelectedPathComponent();

        if (log.isDebugEnabled())
        {
            log.debug("valueChanged : selected node - " + node);
        }

        StyledDocument statsDoc = stats.getStyledDocument();
        try
        {
            statsDoc.remove(0, statsDoc.getLength());
			sampleDataField.setText("");
			results.setText("");
            if (node != null)
            {
                SampleResult res = (SampleResult) node.getUserObject();
                byte[] responseBytes = res.getResponseData();

                if (log.isDebugEnabled())
                {
                    log.debug("valueChanged1 : sample result - " + res);
                }

                if (res != null)
                {
                    // load time label

                    log.debug("valueChanged1 : load time - " + res.getTime());
                    if (res != null && res.getSamplerData() != null)
                    {
                    	String sd;
                    	String rh = res.getRequestHeaders();
                    	if (rh==null)
                    	{
                    		sd=res.getSamplerData().trim();
                    	} else {
							sd=res.getSamplerData().trim()
							   +"\n"+rh;
                    	} 
                        sampleDataField.setText(sd);
                    }

                    statsDoc.insertString(
                        statsDoc.getLength(),
                        "Load time: " + res.getTime() + "\n",
                        null);

                    String responseCode = res.getResponseCode();
                    log.debug(
                        "valueChanged1 : response code - " + responseCode);

                    int responseLevel = 0;
                    if (responseCode != null)
                    {
                        try
                        {
                            responseLevel =
                                Integer.parseInt(responseCode) / 100;
                        }
                        catch (NumberFormatException numberFormatException)
                        {
                            // no need to change the foreground color
                        }
                    }

                    Style style = null;
                    switch (responseLevel)
                    {
                        case 3 :
                            style = statsDoc.getStyle("Redirect");
                            break;
                        case 4 :
                            style = statsDoc.getStyle("ClientError");
                            break;
                        case 5 :
                            style = statsDoc.getStyle("ServerError");
                            break;
                    }
                    statsDoc.insertString(
                        statsDoc.getLength(),
                        "HTTP response code: " + responseCode + "\n",
                        style);

                    // response message label
                    String responseMsgStr = res.getResponseMessage();

                    log.debug(
                        "valueChanged1 : response message - " + responseMsgStr);
                    statsDoc.insertString(
                        statsDoc.getLength(),
                        "HTTP response message: " + responseMsgStr + "\n",
                        null);

					statsDoc.insertString(
						statsDoc.getLength(),
						"\nHTTP response headers:\n" + res.getResponseHeaders() + "\n",
						null);

                    String response = getResponseAsString(res);
                    if (textMode)
                    {
                        showTextResponse(response);
                    }
                    else
                    {
                        showRenderedResponse(response);
                    }
                }
                else if (responseBytes != null)
                {
                    showImage(new ImageIcon(responseBytes));
                }
            }
        }
        catch (BadLocationException exc)
        {
            log.error("Error setting statistics text", exc);
            stats.setText("");
        }
        log.debug("End : valueChanged1");
    }

    private void showImage(Icon image)
    {
        imageLabel.setIcon(image);
        resultsScrollPane.setViewportView(imageLabel);
        textButton.setEnabled(false);
        htmlButton.setEnabled(false);
    }

    protected void showTextResponse(String response)
    {
        results.setContentType("text/plain");
		results.setText(response == null ? "" : response);
        results.setCaretPosition(0);
        resultsScrollPane.setViewportView(results);

        textButton.setEnabled(true);
        htmlButton.setEnabled(true);
    }

    private static String getResponseAsString(SampleResult res)
	{
    	
        byte[] responseBytes = res.getResponseData();
        String response = null;
//        System.out.println("grasDE="+res.getDataEncoding());
//        System.out.println("grasCT="+res.getContentType());
//        System.out.println("grasDT="+res.getDataType());
        if (res.getDataType() != null
            && res.getDataType().equals(SampleResult.TEXT))
        {
            try
            {
				// Showing large strings can be VERY costly, so we will avoid doing so if the response
				// data is larger than 200K. TODO: instead, we could delay doing the result.setText
				// call until the user chooses the "Response data" tab. Plus we could warn the user
				// if this happens and revert the choice if he doesn't confirm he's ready to wait.
				if (responseBytes.length > 200*1024)
				{
					response= 
						("Response too large to be displayed ("+responseBytes.length+" bytes).");
					log.warn("Response too large to display.");
				}
				else
				{
					response = 
						new String(responseBytes,res.getDataEncoding());
				}
            }
            catch (UnsupportedEncodingException err)
            {
            	log.warn("Could not decode response "+err);
				response = 	new String(responseBytes);// Try the default encoding instead
            }
        }
    	return response;
    }

    /**
     * Display the response as text or as rendered HTML.  Change the
     * text on the button appropriate to the current display.
     * 
     * @param e the ActionEvent being processed
     */
    public void actionPerformed(ActionEvent e)
    {
        String command = e.getActionCommand();

        if (command != null
            && (
                command.equals(TEXT_COMMAND)
                ||
                command.equals(HTML_COMMAND)
               )
            )
        {

            textMode = command.equals(TEXT_COMMAND);

            DefaultMutableTreeNode node =
                (DefaultMutableTreeNode) jTree.getLastSelectedPathComponent();

            if (node == null)
            {
                results.setText("");
                return;
            }

            SampleResult res = (SampleResult) node.getUserObject();
            String response = getResponseAsString(res);
            if (textMode)
            {
                showTextResponse(response);
            }
{'fraction_non_alphanumeric': 0.08873284391956592, 'fraction_numerical': 0.00485867290846544, 'mean_word_length': 1.6627006610009443, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5014552', 'n_tokens_mistral': 7463, 'n_tokens_neox': 6800, 'n_words': 1825}