33 | */
34 | /**
35 | * HTTPLangParser is the NetBeans Parser class that ties the snapshot to our
36 | * {@link HTTPLangParserResult}.
37 | *
38 | *
39 | * Whenever NetBeans decides a parse is needed, it calls
40 | * {@link #parse(Snapshot, Task, SourceModificationEvent)}. We then build a new
41 | * {@link HTTPLangParserResult}, run its parse() method, and store it in
42 | * {@code lastResult}.
43 | */
44 | public class HTTPLangParser extends Parser {
45 |
46 | private Result lastResult;
47 |
48 | /**
49 | * This is invoked by the NetBeans parsing API to parse the snapshot.
50 | *
51 | * @param snapshot The current snapshot of the file's contents.
52 | * @param task The parsing task (unused in this simple example).
53 | * @param event SourceModificationEvent with modification info (unused
54 | * here).
55 | * @throws ParseException if something goes wrong
56 | */
57 | @Override
58 | public void parse(Snapshot snapshot, Task task, SourceModificationEvent event) throws ParseException {
59 | // Build a new ParserResult and parse the content
60 | lastResult = new HTTPLangParserResult(snapshot).parse();
61 | }
62 |
63 | /**
64 | * Provides the last parser result created by
65 | * {@link #parse(Snapshot, Task, SourceModificationEvent)}.
66 | *
67 | * @param task The task requesting the result.
68 | * @return The most recent ParserResult for this snapshot.
69 | * @throws ParseException if something fails
70 | */
71 | @Override
72 | public Result getResult(Task task) throws ParseException {
73 | return lastResult;
74 | }
75 |
76 | @Override
77 | public void addChangeListener(ChangeListener changeListener) {
78 | // Not used in this minimal example
79 | }
80 |
81 | @Override
82 | public void removeChangeListener(ChangeListener changeListener) {
83 | // Not used in this minimal example
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/event/TableParamsListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Javier Llorente .
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.javierllorente.netbeans.rest.client.event;
17 |
18 | import com.javierllorente.netbeans.rest.client.ui.UrlPanel;
19 | import com.javierllorente.netbeans.rest.client.ui.TablePanel;
20 | import java.util.Arrays;
21 | import java.util.List;
22 | import java.util.logging.Level;
23 | import java.util.logging.Logger;
24 | import javax.swing.event.DocumentListener;
25 | import javax.swing.event.TableModelEvent;
26 | import javax.swing.event.TableModelListener;
27 |
28 | /**
29 | *
30 | * @author Javier Llorente
31 | */
32 | public class TableParamsListener implements TableModelListener {
33 |
34 | private static final Logger logger = Logger.getLogger(TableParamsListener.class.getName());
35 | private final TablePanel paramsPanel;
36 | private final UrlPanel urlPanel;
37 | private final DocumentListener urlDocumentListener;
38 |
39 | public TableParamsListener(TablePanel paramsPanel, UrlPanel urlPanel,
40 | DocumentListener urlDocumentListener) {
41 | this.paramsPanel = paramsPanel;
42 | this.urlPanel = urlPanel;
43 | this.urlDocumentListener = urlDocumentListener;
44 | }
45 |
46 | @Override
47 | public void tableChanged(TableModelEvent tme) {
48 | if (tme.getType() == TableModelEvent.INSERT || tme.getType() == TableModelEvent.DELETE) {
49 | updateUrlField();
50 | }
51 | }
52 |
53 | private void updateUrlField() {
54 | // urlField.setText() invokes urlDocumentListener.removeUpdate() with
55 | // an empty text in the DocumentEvent, having all rows removed
56 | // even if the urlField is not empty.
57 | // Removing the listener avoids this situation
58 | logger.log(Level.INFO, "urlField = {0}", urlPanel.getUrl());
59 | urlPanel.removeUrlDocumentListener(urlDocumentListener);
60 | urlPanel.setUrl(getUrl() + getParams());
61 | urlPanel.addUrlDocumentListener(urlDocumentListener);
62 |
63 | logger.log(Level.INFO, "updated urlField = {0}", urlPanel.getUrl());
64 | }
65 |
66 | private String getUrl() {
67 | List url = Arrays.asList(urlPanel.getUrl().split("\\?", 2));
68 | return url.get(0);
69 | }
70 |
71 | private String getParams() {
72 | String params = paramsPanel.getRowCount() > 0 ? "?" : "";
73 |
74 | for (int i = 0; i < paramsPanel.getRowCount(); i++) {
75 | params += paramsPanel.getKey(i) + "=" + paramsPanel.getValue(i);
76 | if (i != paramsPanel.getRowCount() - 1) {
77 | params += "&";
78 | }
79 | }
80 |
81 | return params;
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/ui/RestClientOptionsOptionsPanelController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Javier Llorente .
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.javierllorente.netbeans.rest.client.ui;
17 |
18 | import java.beans.PropertyChangeListener;
19 | import java.beans.PropertyChangeSupport;
20 | import javax.swing.JComponent;
21 | import javax.swing.SwingUtilities;
22 | import org.netbeans.spi.options.OptionsPanelController;
23 | import org.openide.util.HelpCtx;
24 | import org.openide.util.Lookup;
25 |
26 | @OptionsPanelController.SubRegistration(
27 | displayName = "#AdvancedOption_DisplayName_RestClientOptions",
28 | keywords = "#AdvancedOption_Keywords_RestClientOptions",
29 | keywordsCategory = "Advanced/RestClientOptions"
30 | )
31 | @org.openide.util.NbBundle.Messages({"AdvancedOption_DisplayName_RestClientOptions=REST Client",
32 | "AdvancedOption_Keywords_RestClientOptions=rest"})
33 | public final class RestClientOptionsOptionsPanelController extends OptionsPanelController {
34 |
35 | private RestClientOptionsPanel panel;
36 | private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
37 | private boolean changed;
38 |
39 | @Override
40 | public void update() {
41 | getPanel().load();
42 | changed = false;
43 | }
44 |
45 | @Override
46 | public void applyChanges() {
47 | SwingUtilities.invokeLater(new Runnable() {
48 | @Override
49 | public void run() {
50 | getPanel().store();
51 | changed = false;
52 | }
53 | });
54 | }
55 |
56 | @Override
57 | public void cancel() {
58 | // need not do anything special, if no changes have been persisted yet
59 | }
60 |
61 | @Override
62 | public boolean isValid() {
63 | return getPanel().valid();
64 | }
65 |
66 | @Override
67 | public boolean isChanged() {
68 | return changed;
69 | }
70 |
71 | @Override
72 | public HelpCtx getHelpCtx() {
73 | return null; // new HelpCtx("...ID") if you have a help set
74 | }
75 |
76 | @Override
77 | public JComponent getComponent(Lookup masterLookup) {
78 | return getPanel();
79 | }
80 |
81 | @Override
82 | public void addPropertyChangeListener(PropertyChangeListener l) {
83 | pcs.addPropertyChangeListener(l);
84 | }
85 |
86 | @Override
87 | public void removePropertyChangeListener(PropertyChangeListener l) {
88 | pcs.removePropertyChangeListener(l);
89 | }
90 |
91 | private RestClientOptionsPanel getPanel() {
92 | if (panel == null) {
93 | panel = new RestClientOptionsPanel(this);
94 | }
95 | return panel;
96 | }
97 |
98 | void changed() {
99 | if (!changed) {
100 | changed = true;
101 | pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true);
102 | }
103 | pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null);
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/ui/UrlPanel.form:
--------------------------------------------------------------------------------
1 |
2 |
3 |
72 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/util/HttpFileUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2025 Christian Lenz
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.javierllorente.netbeans.rest.client.util;
17 |
18 | import java.io.File;
19 | import java.io.IOException;
20 | import java.nio.file.Files;
21 | import java.text.SimpleDateFormat;
22 | import java.util.Date;
23 | import javax.swing.JEditorPane;
24 | import javax.swing.SwingUtilities;
25 | import javax.swing.text.StyledDocument;
26 | import org.openide.cookies.EditorCookie;
27 | import org.openide.cookies.OpenCookie;
28 | import org.openide.filesystems.FileObject;
29 | import org.openide.filesystems.FileUtil;
30 | import org.openide.loaders.DataObject;
31 |
32 | /**
33 | * Utility class for creating and opening .http files in the editor.
34 | */
35 | public class HttpFileUtils {
36 |
37 | private HttpFileUtils() {
38 | // Utility class
39 | }
40 |
41 | /**
42 | * Creates a new .http file with the given content and opens it in the editor.
43 | *
44 | * @param httpContent The HTTP request content to write to the file
45 | * @param caretPosition The position where to place the cursor, or -1 to place it at the end
46 | * @throws IOException if file creation or opening fails
47 | */
48 | public static void createAndOpenHttpFile(String httpContent, int caretPosition) throws IOException {
49 | if (httpContent == null) {
50 | httpContent = "";
51 | }
52 |
53 | // Create .http file inside netbeans-rest-client folder in user directory
54 | File userDir = new File(System.getProperty("user.home"), ".netbeans/netbeans-rest-client");
55 | userDir.mkdirs();
56 | String timestamp = new SimpleDateFormat("yyyyMMdd'T'HHmmss").format(new Date());
57 | File httpFile = new File(userDir, "request-" + timestamp + ".http");
58 | Files.writeString(httpFile.toPath(), httpContent);
59 |
60 | // Open the file in the editor
61 | FileObject fileObject = FileUtil.toFileObject(httpFile);
62 | if (fileObject != null) {
63 | DataObject dataObject = DataObject.find(fileObject);
64 | OpenCookie openCookie = dataObject.getLookup().lookup(OpenCookie.class);
65 | EditorCookie editor = dataObject.getLookup().lookup(EditorCookie.class);
66 |
67 | if (openCookie == null) {
68 | return;
69 | }
70 |
71 | openCookie.open();
72 | editor.openDocument();
73 |
74 | // Position cursor if specified
75 | if (caretPosition >= 0) {
76 | final int finalCaretPosition = caretPosition;
77 |
78 | SwingUtilities.invokeLater(() -> {
79 | StyledDocument doc = editor.getDocument();
80 | if (doc == null) {
81 | return;
82 | }
83 |
84 | JEditorPane[] panes = editor.getOpenedPanes();
85 | if (panes == null || panes.length == 0) {
86 | return;
87 | }
88 |
89 | int pos = Math.min(finalCaretPosition, doc.getLength());
90 | panes[0].setCaretPosition(pos);
91 | });
92 | }
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/http/editor/navigator/HTTPLangStructureScanner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 Christian Lenz .
3 | *
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 | package com.javierllorente.netbeans.rest.client.http.editor.navigator;
22 |
23 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.HTTPLangParserResult;
24 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.antlr.HTTPParser;
25 | import java.util.ArrayList;
26 | import java.util.Collections;
27 | import java.util.List;
28 | import java.util.Map;
29 | import org.netbeans.modules.csl.api.OffsetRange;
30 | import org.netbeans.modules.csl.api.StructureItem;
31 | import org.netbeans.modules.csl.api.StructureScanner;
32 | import org.netbeans.modules.csl.spi.ParserResult;
33 | import org.openide.filesystems.FileObject;
34 |
35 | /**
36 | *
37 | * @author Christian Lenz
38 | */
39 | /**
40 | * HTTPLangStructureScanner scans the parsed HTTP file (HTTPLangParserResult)
41 | * and produces structure items for each request line.
42 | *
43 | *
44 | * These structure items are instances of HTTPLangStructureItem and will be
45 | * displayed in the Navigator (Outline) of the editor. Each item represents a
46 | * request line in the HTTP file.
47 | */
48 | public class HTTPLangStructureScanner implements StructureScanner {
49 |
50 | /**
51 | * Scans the parser result and returns a list of structure items.
52 | *
53 | * @param result the ParserResult (expected to be a HTTPLangParserResult)
54 | * @return a list of structure items, or an empty list if none found
55 | */
56 | @Override
57 | public List extends StructureItem> scan(ParserResult result) {
58 | // Überprüfen, ob das ParserResult vom erwarteten Typ ist
59 | if (!(result instanceof HTTPLangParserResult)) {
60 | return Collections.emptyList();
61 | }
62 | HTTPLangParserResult httpResult = (HTTPLangParserResult) result;
63 |
64 | // Stelle sicher, dass der Parser bereits ausgeführt wurde
65 | httpResult.parse();
66 |
67 | List items = new ArrayList<>();
68 | // Hole das FileObject aus dem Snapshot des ParserResults
69 | FileObject fo = httpResult.getSnapshot().getSource().getFileObject();
70 |
71 | // Hole alle RequestLine-Kontexte, die im ParserResult gefunden wurden
72 | List requestLines = httpResult.getRequestLineContexts();
73 | if (requestLines != null) {
74 | for (HTTPParser.RequestLineContext reqLine : requestLines) {
75 | // Nutze den Text der RequestLine als Display-Namen. Kürze ihn, falls er zu lang ist.
76 | String displayName = reqLine.getText();
77 | if (displayName.length() > 40) {
78 | displayName = displayName.substring(0, 40) + "...";
79 | }
80 | // Erstelle ein StructureItem für diese RequestLine.
81 | items.add(new HTTPLangStructureItem(fo, displayName, reqLine));
82 | }
83 | }
84 | return items;
85 | }
86 |
87 | @Override
88 | public Map> folds(ParserResult pr) {
89 | return Collections.emptyMap();
90 | }
91 |
92 | @Override
93 | public Configuration getConfiguration() {
94 | return new Configuration(true, false);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/ui/LineNumberComponent.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Christian Lenz .
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.javierllorente.netbeans.rest.client.ui;
17 |
18 | import java.awt.*;
19 | import java.awt.geom.Rectangle2D;
20 | import javax.swing.*;
21 | import javax.swing.text.*;
22 |
23 | /**
24 | *
25 | * @author Christian Lenz
26 | */
27 | public class LineNumberComponent extends JComponent {
28 |
29 | private final JTextComponent textComponent;
30 | private int lastDigits = 0;
31 |
32 | public LineNumberComponent(JTextComponent textComponent) {
33 | this.textComponent = textComponent;
34 | Font font = textComponent.getFont();
35 | setFont(font);
36 | setPreferredSize(new Dimension(40, textComponent.getHeight()));
37 |
38 | // adjustWidth();
39 | }
40 |
41 | @Override
42 | protected void paintComponent(Graphics g) {
43 | super.paintComponent(g);
44 |
45 | Graphics2D g2d = (Graphics2D) g;
46 |
47 | g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
48 | g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
49 |
50 | g2d.setColor(Color.GRAY);
51 |
52 | try {
53 | JViewport viewport = (JViewport) SwingUtilities.getAncestorOfClass(JViewport.class, textComponent);
54 | if (viewport != null) {
55 | Point viewPosition = viewport.getViewPosition();
56 | Rectangle clip = g2d.getClipBounds();
57 |
58 | int startOffset = textComponent.viewToModel2D(new Point(0, clip.y + viewPosition.y));
59 | int endOffset = textComponent.viewToModel2D(new Point(0, clip.y + clip.height + viewPosition.y));
60 |
61 | Document doc = textComponent.getDocument();
62 | Element root = doc.getDefaultRootElement();
63 |
64 | int startLine = root.getElementIndex(startOffset);
65 | int endLine = root.getElementIndex(endOffset);
66 |
67 | FontMetrics fontMetrics = g2d.getFontMetrics();
68 | int fontAscent = fontMetrics.getAscent();
69 |
70 | for (int line = startLine; line <= endLine; line++) {
71 | int lineStartOffset = root.getElement(line).getStartOffset();
72 | Rectangle2D r = textComponent.modelToView2D(lineStartOffset);
73 |
74 | if (r != null && r.getY() - viewPosition.y + r.getHeight() > clip.y) {
75 | String lineNumber = String.valueOf(line + 1);
76 | int y = (int) Math.round(r.getY() - viewPosition.y + fontAscent);
77 | g2d.drawString(lineNumber, getWidth() - fontMetrics.stringWidth(lineNumber) - 5, y);
78 | }
79 | }
80 | }
81 | } catch (BadLocationException e) {
82 | e.printStackTrace();
83 | }
84 | }
85 |
86 | private void adjustWidth() {
87 | int lines = getLineCount();
88 | int digits = Math.max(String.valueOf(lines).length(), 1);
89 |
90 | if (digits != lastDigits) {
91 | int width = getFontMetrics(getFont()).stringWidth("0") * digits + 16;
92 | setPreferredSize(new Dimension(width, getHeight()));
93 | lastDigits = digits;
94 | revalidate();
95 | }
96 | }
97 |
98 | private int getLineCount() {
99 | return textComponent.getDocument().getDefaultRootElement().getElementCount();
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/hyperlink/RestHyperlinkProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017-2022 The Apache Software Foundation
3 | * Copyright 2023 Javier Llorente
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package com.javierllorente.netbeans.rest.client.hyperlink;
18 |
19 | import java.awt.Toolkit;
20 | import java.net.MalformedURLException;
21 | import java.net.URL;
22 | import java.util.EnumSet;
23 | import java.util.Set;
24 | import javax.swing.text.BadLocationException;
25 | import javax.swing.text.Document;
26 | import org.netbeans.api.editor.document.LineDocumentUtils;
27 | import org.netbeans.api.editor.mimelookup.MimeRegistration;
28 | import org.netbeans.api.editor.mimelookup.MimeRegistrations;
29 | import org.netbeans.editor.BaseDocument;
30 | import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProviderExt;
31 | import org.netbeans.lib.editor.hyperlink.spi.HyperlinkType;
32 | import org.netbeans.lib.editor.util.swing.DocumentUtilities;
33 | import org.openide.util.Exceptions;
34 | import org.openide.util.Lookup;
35 | import com.javierllorente.netbeans.rest.client.parsers.Parser;
36 | import com.javierllorente.netbeans.rest.client.editor.RestMediaType;
37 |
38 | /**
39 | *
40 | * @author Jan Lahoda
41 | * @author Javier Llorente
42 | */
43 | @MimeRegistrations({
44 | @MimeRegistration(mimeType = RestMediaType.JSON, service = HyperlinkProviderExt.class),
45 | @MimeRegistration(mimeType = RestMediaType.XML, service = HyperlinkProviderExt.class)
46 | })
47 | public class RestHyperlinkProvider implements HyperlinkProviderExt {
48 |
49 | @Override
50 | public Set getSupportedHyperlinkTypes() {
51 | return EnumSet.of(HyperlinkType.GO_TO_DECLARATION);
52 | }
53 |
54 | @Override
55 | public boolean isHyperlinkPoint(Document doc, int offset, HyperlinkType type) {
56 | return getHyperlinkSpan(doc, offset, type) != null;
57 | }
58 |
59 | @Override
60 | public int[] getHyperlinkSpan(Document doc, int offset, HyperlinkType type) {
61 | if (!(doc instanceof BaseDocument)) {
62 | return null;
63 | }
64 |
65 | try {
66 | BaseDocument bdoc = (BaseDocument) doc;
67 | int start = LineDocumentUtils.getLineStart(bdoc, offset);
68 | int end = LineDocumentUtils.getLineEnd(bdoc, offset);
69 |
70 | for (int[] span : Parser.recognizeURLs(DocumentUtilities.getText(doc, start, end - start))) {
71 | if (span[0] + start <= offset && offset <= span[1] + start) {
72 | return new int[]{
73 | span[0] + start,
74 | span[1] + start
75 | };
76 | }
77 | }
78 | } catch (BadLocationException ex) {
79 | Exceptions.printStackTrace(ex);
80 | }
81 |
82 | return null;
83 | }
84 |
85 | @Override
86 | public void performClickAction(Document doc, int offset, HyperlinkType type) {
87 | int[] span = getHyperlinkSpan(doc, offset, type);
88 |
89 | if (span == null) {
90 | Toolkit.getDefaultToolkit().beep();
91 | return;
92 | }
93 |
94 | RestURLDisplayer urlDisplayer = Lookup.getDefault().lookup(RestURLDisplayer.class);
95 |
96 | try {
97 | String urlText = doc.getText(span[0], span[1] - span[0]);
98 | urlDisplayer.showURL(new URL(urlText));
99 | } catch (BadLocationException | MalformedURLException ex) {
100 | Exceptions.printStackTrace(ex);
101 | }
102 | }
103 |
104 | @Override
105 | public String getTooltipText(Document doc, int offset, HyperlinkType type) {
106 | return null;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/util/FormatUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022-2024 Javier Llorente .
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.javierllorente.netbeans.rest.util;
17 |
18 | import jakarta.json.Json;
19 | import jakarta.json.JsonArray;
20 | import jakarta.json.JsonObject;
21 | import jakarta.json.JsonStructure;
22 | import jakarta.json.JsonWriter;
23 | import jakarta.json.JsonWriterFactory;
24 | import jakarta.json.stream.JsonGenerator;
25 | import java.io.IOException;
26 | import java.io.InputStream;
27 | import java.io.StringReader;
28 | import java.io.StringWriter;
29 | import java.nio.charset.StandardCharsets;
30 | import java.util.HashMap;
31 | import java.util.Map;
32 | import javax.xml.XMLConstants;
33 | import javax.xml.transform.OutputKeys;
34 | import javax.xml.transform.Source;
35 | import javax.xml.transform.Transformer;
36 | import javax.xml.transform.TransformerException;
37 | import javax.xml.transform.TransformerFactory;
38 | import javax.xml.transform.stream.StreamResult;
39 | import javax.xml.transform.stream.StreamSource;
40 | import org.jsoup.Jsoup;
41 | import org.jsoup.nodes.Document;
42 | import org.openide.util.Exceptions;
43 |
44 | /**
45 | *
46 | * @author Javier Llorente
47 | */
48 | public class FormatUtils {
49 |
50 | private FormatUtils() {
51 | }
52 |
53 | public static String jsonPrettyFormat(JsonStructure jsonStructure) {
54 | Map map = new HashMap<>();
55 | map.put(JsonGenerator.PRETTY_PRINTING, true);
56 | JsonWriterFactory writerFactory = Json.createWriterFactory(map);
57 | StringWriter stringWriter = new StringWriter();
58 | try (final JsonWriter jsonWriter = writerFactory.createWriter(stringWriter)) {
59 | if (jsonStructure instanceof JsonObject) {
60 | jsonWriter.writeObject((JsonObject) jsonStructure);
61 | } else {
62 | jsonWriter.writeArray((JsonArray) jsonStructure);
63 | }
64 | }
65 | return stringWriter.toString();
66 | }
67 |
68 | public static String xmlPrettyFormat(String input) {
69 | Source xmlInput = new StreamSource(new StringReader(input));
70 | StringWriter stringWriter = new StringWriter();
71 | StreamResult xmlOutput = new StreamResult(stringWriter);
72 | TransformerFactory transformerFactory = TransformerFactory.newInstance();
73 | transformerFactory.setAttribute("indent-number", 2);
74 | transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
75 | transformerFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "");
76 | try {
77 | Transformer transformer = transformerFactory.newTransformer(readXsl());
78 | transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
79 | transformer.setOutputProperty(OutputKeys.INDENT, "yes");
80 | transformer.transform(xmlInput, xmlOutput);
81 | } catch (TransformerException ex) {
82 | Exceptions.printStackTrace(ex);
83 | }
84 | return xmlOutput.getWriter().toString();
85 | }
86 |
87 | private static Source readXsl() {
88 | InputStream inputStream = FormatUtils.class.getResourceAsStream("prettyprint.xsl");
89 | Source xslSource = null;
90 | try {
91 | if (inputStream != null) {
92 | String xsl = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
93 | inputStream.close();
94 | xslSource = new StreamSource(new StringReader(xsl));
95 | }
96 | } catch (IOException ex) {
97 | Exceptions.printStackTrace(ex);
98 | }
99 | return xslSource;
100 | }
101 |
102 | public static String htmlPrettyFormat(String input) {
103 | Document doc = Jsoup.parse(input);
104 | return doc.toString();
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/ResponseSidebarManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 Christian Lenz .
3 | *
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar;
22 |
23 | import jakarta.ws.rs.core.MultivaluedMap;
24 | import java.util.WeakHashMap;
25 | import javax.swing.SwingUtilities;
26 | import javax.swing.text.JTextComponent;
27 |
28 | /**
29 | * Manager for controlling the response sidebar programmatically.
30 | * This singleton allows RequestProcessor and other classes to show responses
31 | * in the sidebar.
32 | *
33 | * @author Christian Lenz
34 | */
35 | public class ResponseSidebarManager {
36 |
37 | private static final ResponseSidebarManager INSTANCE = new ResponseSidebarManager();
38 | private final WeakHashMap sidebars;
39 |
40 | private ResponseSidebarManager() {
41 | this.sidebars = new WeakHashMap<>();
42 | }
43 |
44 | public static ResponseSidebarManager getInstance() {
45 | return INSTANCE;
46 | }
47 |
48 | /**
49 | * Register a sidebar panel for a specific text component.
50 | * Called by ResponseSideBarFactory when creating sidebars.
51 | */
52 | public void registerSidebar(JTextComponent textComponent, ResponseSidebarPanel panel) {
53 | sidebars.put(textComponent, panel);
54 | }
55 |
56 | /**
57 | * Show a response in the sidebar for the given text component.
58 | *
59 | * @param textComponent The editor component
60 | * @param response The response content
61 | * @param contentType The content type (e.g., "application/json")
62 | */
63 | public void showResponse(JTextComponent textComponent, String response, String contentType) {
64 | showResponse(textComponent, response, contentType, null);
65 | }
66 |
67 | /**
68 | * Show a response in the sidebar for the given text component with headers.
69 | *
70 | * @param textComponent The editor component
71 | * @param response The response content
72 | * @param contentType The content type (e.g., "application/json")
73 | * @param headers The response headers
74 | */
75 | public void showResponse(JTextComponent textComponent, String response, String contentType, MultivaluedMap headers) {
76 | if (textComponent == null) {
77 | return;
78 | }
79 |
80 | // Ensure UI updates happen on the Event Dispatch Thread
81 | SwingUtilities.invokeLater(() -> {
82 | ResponseSidebarPanel panel = sidebars.get(textComponent);
83 | if (panel != null) {
84 | try {
85 | panel.showResponse(response, contentType, headers);
86 | } catch (Exception e) {
87 | // Log but don't propagate exceptions
88 | System.err.println("Error showing response in sidebar: " + e.getMessage());
89 | e.printStackTrace();
90 | }
91 | }
92 | });
93 | }
94 |
95 | /**
96 | * Hide the sidebar for the given text component.
97 | */
98 | public void hideSidebar(JTextComponent textComponent) {
99 | if (textComponent == null) {
100 | return;
101 | }
102 |
103 | SwingUtilities.invokeLater(() -> {
104 | ResponseSidebarPanel panel = sidebars.get(textComponent);
105 | if (panel != null) {
106 | try {
107 | panel.setVisible(false);
108 | } catch (Exception e) {
109 | System.err.println("Error hiding sidebar: " + e.getMessage());
110 | e.printStackTrace();
111 | }
112 | }
113 | });
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/parsers/UrlParamsParser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Javier Llorente .
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.javierllorente.netbeans.rest.client.parsers;
17 |
18 | import com.javierllorente.netbeans.rest.client.ui.TablePanel;
19 | import java.util.Arrays;
20 | import java.util.List;
21 | import java.util.logging.Level;
22 | import java.util.logging.Logger;
23 |
24 | /**
25 | *
26 | * @author Javier Llorente
27 | */
28 | public class UrlParamsParser {
29 |
30 | private static final Logger logger = Logger.getLogger(UrlParamsParser.class.getName());
31 | private final TablePanel paramsPanel;
32 |
33 | public UrlParamsParser(TablePanel paramsPanel) {
34 | this.paramsPanel = paramsPanel;
35 | }
36 |
37 | public void processChanges(String urlFieldUpdate) {
38 | logger.log(Level.INFO, "processChanges() urlFieldUpdate = {0}", urlFieldUpdate);
39 | List url = Arrays.asList(urlFieldUpdate.split("\\?", 2));
40 |
41 | if (url.size() > 1) {
42 | String urlFieldParams = url.get(1);
43 | List params = Arrays.asList(urlFieldParams.split("\\&", -1));
44 |
45 | if (params.size() >= paramsPanel.getRowCount()) {
46 | // Add/edit rows
47 |
48 | for (int i = 0; i < params.size(); i++) {
49 | List entry = Arrays.asList(params.get(i).split("=", 2));
50 |
51 | if (params.size() > paramsPanel.getRowCount()) {
52 | paramsPanel.addRow(entry.get(0), entry.size() > 1 ? entry.get(1) : "");
53 | paramsPanel.selectLastItem();
54 | paramsPanel.showLastItem();
55 | }
56 |
57 | if (!paramsPanel.getKey(i).equals(entry.get(0))
58 | || !paramsPanel.getValue(i).equals(entry.size() > 1 ? entry.get(1) : "")) {
59 | paramsPanel.editRow(i, entry.get(0),
60 | entry.size() > 1 ? entry.get(1) : "");
61 | paramsPanel.changeSelection(i, 1);
62 | logger.log(Level.INFO, "changeSelection to row: {0}", i);
63 | }
64 | }
65 |
66 | } else if (params.size() < paramsPanel.getRowCount()) {
67 | // Remove rows
68 |
69 | for (int i = 0; i < paramsPanel.getRowCount(); i++) {
70 | String row = paramsPanel.getKey(i) + "=" + paramsPanel.getValue(i);
71 | if (row.equals("=")) {
72 | row = "";
73 | }
74 |
75 | if (i <= params.size() - 1 && !params.get(i).equals(row)) {
76 | paramsPanel.removeRow(i);
77 | paramsPanel.changeSelection(i, 1);
78 |
79 | if (i <= params.size() - 1 && !params.get(i).equals(row)) {
80 | List entry = Arrays.asList(params.get(i).split("=", 2));
81 | paramsPanel.editRow(i, entry.get(0),
82 | entry.size() > 1 ? entry.get(1) : "");
83 | paramsPanel.changeSelection(i, 1);
84 | logger.log(Level.INFO, "changeSelection to row: {0}", i);
85 | }
86 |
87 | } else if (i > params.size() - 1
88 | && params.size() < paramsPanel.getRowCount()) {
89 | paramsPanel.removeRow(i);
90 | paramsPanel.changeSelection(--i, 1);
91 | }
92 | }
93 |
94 | }
95 |
96 |
97 | } else if (!paramsPanel.isEmpty()) {
98 | logger.info("removeAllRows!");
99 | paramsPanel.removeAllRows();
100 | }
101 |
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/src/main/antlr4/HTTPLexer.g4:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 Christian Lenz .
3 | *
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 |
22 | /**
23 | *
24 | * @author Christian Lenz
25 | */
26 | lexer grammar HTTPLexer;
27 |
28 | tokens { OPEN_BLOCK_BRAKET, CLOSE_BLOCK_BRAKET }
29 |
30 | WS: [ \t];
31 |
32 | // For JSON and text body - only consume newlines, not body content
33 | BODY_START_WITH_BLANK: ('\r'? '\n') WS* {_input.LA(1) == '{'}? -> pushMode(BODY_CONTENT);
34 | GENERIC_BODY_START: ('\r'? '\n') ('\r'? '\n') {_input.LA(1) >= 'a' && _input.LA(1) <= 'z' || _input.LA(1) == '<'}? -> pushMode(BODY_CONTENT);
35 |
36 | NEWLINE: ('\r'? '\n');
37 | REQUEST_SEPARATOR : '###' (~[\r\n])*;
38 | COMMENT: ('#' | '//') (~[\r\n])*;
39 |
40 | fragment OPTIONAL_WS: WS*;
41 | fragment REQUIRED_WS: WS+;
42 |
43 | METHOD
44 | : 'GET'
45 | | 'HEAD'
46 | | 'POST'
47 | | 'PUT'
48 | | 'DELETE'
49 | | 'CONNECT'
50 | | 'PATCH'
51 | | 'OPTIONS'
52 | | 'TRACE'
53 | ;
54 |
55 | HTTP_PROTOCOL : 'HTTP';
56 |
57 | COLON: ':';
58 | DOT: '.';
59 | COMMA: ',';
60 | SLASH: '/';
61 | QUESTION_MARK: '?';
62 | HASH: '#';
63 | ASTERISK: '*';
64 | EQUAL: '=';
65 | AMPERSAND: '&';
66 | PERCENT: '%';
67 | OPEN_BRAKET: '[';
68 | CLOSE_BRAKET: ']';
69 | QUOTE: '"';
70 |
71 | fragment DIGIT: [0-9];
72 | DIGITS: DIGIT+;
73 |
74 | SCHEME
75 | : 'http'
76 | | 'https'
77 | | 'HTTPS'
78 | | 'ws'
79 | | 'WS'
80 | | 'wss'
81 | | 'WSS'
82 | | 'ftp'
83 | | 'FTP'
84 | | 'ftps'
85 | | 'FTPS'
86 | | 'sftp'
87 | | 'SFTP'
88 | | 'ldap'
89 | | 'LDAP'
90 | | 'ldaps'
91 | | 'LDAPS'
92 | | 'imap'
93 | | 'IMAP'
94 | | 'imaps'
95 | | 'IMAPS'
96 | | 'smtp'
97 | | 'SMTP'
98 | | 'smtps'
99 | | 'SMTPS'
100 | | 'pop3'
101 | | 'POP3'
102 | | 'pop3s'
103 | | 'POP3S'
104 | | 'file'
105 | | 'FILE'
106 | | 'telnet'
107 | | 'TELNET'
108 | | 'rtsp'
109 | | 'RTSP'
110 | | 'smb'
111 | | 'SMB'
112 | | 'nfs'
113 | | 'NFS'
114 | | 'git'
115 | | 'GIT'
116 | | 'mms'
117 | | 'MMS'
118 | | 'news'
119 | | 'NEWS'
120 | | 'urn'
121 | | 'URN'
122 | | 'data'
123 | | 'DATA'
124 | ;
125 |
126 | fragment LINE_TAIL: (~[\r\n])* NEWLINE;
127 |
128 | ALPHA_CHARS: [a-zA-Z]+;
129 |
130 | DASH: '-';
131 |
132 | UNDERSCORE: '_';
133 |
134 | SCHEME_SEPARATOR : '://';
135 |
136 | INPUT_FILE_REF : '<' REQUIRED_WS LINE_TAIL;
137 |
138 | RESPONSE_HANDLER_SCRIPT_START : '>' REQUIRED_WS '{%';
139 | RESPONSE_HANDLER_SCRIPT_END : '%}';
140 | RESPONSE_HANDLER_FILE_REF : '>' REQUIRED_WS LINE_TAIL;
141 |
142 | SCRIPT_CONTENT : . ;
143 |
144 | // Environment variables
145 | ENV_VARIABLE : '{{' OPTIONAL_WS (ALPHA_CHARS DIGITS)* OPTIONAL_WS '}}';
146 |
147 | // Multipart-Form-Data
148 | MULTIPART_BOUNDARY : '--' (~[\r\n])* '-' '--';
149 | MULTIPART_PART : '--' (~[\r\n])* '-';
150 |
151 | // Error case: Body without blank line - keep consuming '{'
152 | BODY_START_NO_BLANK: '{' -> pushMode(BODY_CONTENT);
153 |
154 | // Ignore everything else
155 | ERROR : .;
156 |
157 | // BODY_CONTENT mode: Accepts JSON, text, HTML, XML, CSV, etc.
158 | mode BODY_CONTENT;
159 |
160 | JSON_LBRACE : '{' -> type(OPEN_BLOCK_BRAKET) ;
161 | JSON_RBRACE : '}' -> type(CLOSE_BLOCK_BRAKET), popMode ;
162 | JSON_LBRACK : '[' -> type(OPEN_BRAKET) ;
163 | JSON_RBRACK : ']' -> type(CLOSE_BRAKET) ;
164 | JSON_COLON : ':' -> type(COLON) ;
165 | JSON_COMMA : ',' -> type(COMMA) ;
166 |
167 | STRING
168 | : '"' ( '\\' . | ~["\\\r\n] )* '"'
169 | ;
170 |
171 | NUMBER
172 | : '-'? DIGIT+ ('.' DIGIT+)? ([eE][+-]? DIGIT+)?
173 | ;
174 |
175 | TRUE : 'true' ;
176 | FALSE : 'false';
177 | NULL : 'null' ;
178 |
179 | JSON_NEWLINE_SEPARATOR : ('\r'? '\n') WS* '###' (~[\r\n])* -> type(REQUEST_SEPARATOR), popMode;
180 | JSON_NEWLINE : ('\r'? '\n') -> type(NEWLINE);
181 | JSON_WS : [ \t]+ -> type(WS);
182 |
183 | // Generic body content for non-JSON bodies (text, HTML, XML, CSV, etc.)
184 | BODY_TEXT : ~[\r\n]+ ;
185 |
186 | JSON_EOF_CLEANUP
187 | : EOF -> popMode, type(EOF)
188 | ;
189 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/ui/RestClientOptionsPanel.form:
--------------------------------------------------------------------------------
1 |
2 |
3 |
23 |
24 |
106 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/ui/UrlPanel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Javier Llorente .
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.javierllorente.netbeans.rest.client.ui;
17 |
18 | import java.awt.event.ActionListener;
19 | import java.awt.event.FocusListener;
20 | import java.awt.event.KeyListener;
21 | import javax.swing.SwingUtilities;
22 | import javax.swing.event.DocumentListener;
23 |
24 | /**
25 | *
26 | * @author Javier Llorente
27 | */
28 | public class UrlPanel extends javax.swing.JPanel {
29 |
30 | /**
31 | * Creates new form UrlPanel
32 | */
33 | public UrlPanel() {
34 | initComponents();
35 | }
36 |
37 | public String getUrl() {
38 | return urlTextField.getText();
39 | }
40 |
41 | public void setUrl(String url) {
42 | urlTextField.setText(url);
43 | }
44 |
45 | public void moveCaretToEnd() {
46 | urlTextField.setCaretPosition(urlTextField.getText().length());
47 | }
48 |
49 | public String getDisplayUrl() {
50 | String url = urlTextField.getText();
51 | if (url.length() > 19) {
52 | url = url.substring(0, 20) + "...";
53 | }
54 | return url;
55 | }
56 |
57 | public void addUrlDocumentListener(DocumentListener dl) {
58 | urlTextField.getDocument().addDocumentListener(dl);
59 | }
60 |
61 | public void removeUrlDocumentListener(DocumentListener dl) {
62 | urlTextField.getDocument().removeDocumentListener(dl);
63 | }
64 |
65 | public void addUrlFocusListener(FocusListener fl) {
66 | urlTextField.addFocusListener(fl);
67 | }
68 |
69 | public void requestUrlFocus() {
70 | SwingUtilities.invokeLater(() -> {
71 | urlTextField.requestFocus();
72 | });
73 | }
74 |
75 | public String getRequestMethod() {
76 | return methodComboBox.getSelectedItem().toString();
77 | }
78 |
79 | public void setRequestMethod(String method) {
80 | methodComboBox.setSelectedItem(method);
81 | }
82 |
83 | public void addSendButtonActionListener(ActionListener al) {
84 | sendButton.addActionListener(al);
85 | }
86 |
87 | public void addComboBoxActionListener(ActionListener al) {
88 | methodComboBox.addActionListener(al);
89 | }
90 |
91 | public void addUrlKeyListener(KeyListener kl) {
92 | urlTextField.addKeyListener(kl);
93 | }
94 |
95 | /**
96 | * This method is called from within the constructor to initialize the form.
97 | * WARNING: Do NOT modify this code. The content of this method is always
98 | * regenerated by the Form Editor.
99 | */
100 | @SuppressWarnings("unchecked")
101 | // //GEN-BEGIN:initComponents
102 | private void initComponents() {
103 |
104 | methodComboBox = new javax.swing.JComboBox<>();
105 | urlTextField = new javax.swing.JTextField();
106 | urlTextField.putClientProperty("JTextField.selectAllOnFocusPolicy", "never");
107 | sendButton = new javax.swing.JButton();
108 |
109 | methodComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "GET", "POST", "PUT", "PATCH", "DELETE" }));
110 |
111 | org.openide.awt.Mnemonics.setLocalizedText(sendButton, "Send");
112 |
113 | javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
114 | this.setLayout(layout);
115 | layout.setHorizontalGroup(
116 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
117 | .addGroup(layout.createSequentialGroup()
118 | .addContainerGap()
119 | .addComponent(methodComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
120 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
121 | .addComponent(urlTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 632, Short.MAX_VALUE)
122 | .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
123 | .addComponent(sendButton)
124 | .addContainerGap())
125 | );
126 | layout.setVerticalGroup(
127 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
128 | .addGroup(layout.createSequentialGroup()
129 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
130 | .addComponent(methodComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
131 | .addComponent(urlTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
132 | .addComponent(sendButton))
133 | .addGap(0, 0, Short.MAX_VALUE))
134 | );
135 | }// //GEN-END:initComponents
136 |
137 |
138 | // Variables declaration - do not modify//GEN-BEGIN:variables
139 | private javax.swing.JComboBox methodComboBox;
140 | private javax.swing.JButton sendButton;
141 | private javax.swing.JTextField urlTextField;
142 | // End of variables declaration//GEN-END:variables
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/http/editor/navigator/HTTPLangStructureItem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 Christian Lenz .
3 | *
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 | package com.javierllorente.netbeans.rest.client.http.editor.navigator;
22 |
23 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.coloring.HTTPTokenId;
24 | import java.util.Collections;
25 | import java.util.List;
26 | import java.util.Set;
27 | import javax.swing.ImageIcon;
28 | import org.antlr.v4.runtime.ParserRuleContext;
29 | import org.netbeans.modules.csl.api.ElementHandle;
30 | import org.netbeans.modules.csl.api.ElementKind;
31 | import org.netbeans.modules.csl.api.HtmlFormatter;
32 | import org.netbeans.modules.csl.api.Modifier;
33 | import org.netbeans.modules.csl.api.OffsetRange;
34 | import org.netbeans.modules.csl.api.StructureItem;
35 | import org.netbeans.modules.csl.spi.ParserResult;
36 | import org.openide.filesystems.FileObject;
37 | import org.openide.util.ImageUtilities;
38 |
39 | /**
40 | *
41 | * @author Christian Lenz
42 | */
43 | /**
44 | * HTTPLangStructureItem represents a single request line in an HTTP file.\n It
45 | * is used by the StructureScanner to display items in the Navigator.\n
46 | */
47 | public class HTTPLangStructureItem implements StructureItem, ElementHandle {
48 |
49 | private final String name;
50 | private final int startPosition;
51 | private final int stopPosition;
52 | private final FileObject fo;
53 |
54 | /**
55 | * Constructs a new structure item with the given name and offset range.
56 | *
57 | * @param name The display name for this structure item.\n
58 | */
59 | // Im Konstruktor:
60 | public HTTPLangStructureItem(FileObject fo, String name, ParserRuleContext context) {
61 | this.name = name;
62 | // Add null checks for context and its start/stop tokens
63 | this.startPosition = (context != null && context.start != null) ? context.start.getStartIndex() : 0; // Default to 0 if null
64 | this.stopPosition = (context != null && context.stop != null) ? context.stop.getStopIndex() + 1 : this.startPosition; // Default to start if stop is null
65 | this.fo = fo;
66 |
67 | // Log warning if context or tokens were null
68 | if (context == null || context.start == null || context.stop == null) {
69 | // Use your logger if available, otherwise System.out
70 | System.err.println("Warning: HTTPLangStructureItem created with null context or tokens for name: " + name);
71 | }
72 | }
73 |
74 | /**
75 | * Returns the display name of the structure item.
76 | *
77 | * @return The name.
78 | */
79 | @Override
80 | public String getName() {
81 | return name;
82 | }
83 |
84 | /**
85 | * Returns the kind of element. Hier kann man beispielsweise REQUEST als\n
86 | * eigenen Typ definieren, hier nutzen wir OTHER als Platzhalter.
87 | *
88 | * @return The element kind.
89 | */
90 | @Override
91 | public ElementKind getKind() {
92 | return ElementKind.OTHER;
93 | }
94 |
95 | /**
96 | * Returns the text used for sorting structure items.
97 | * Returns the position padded with zeros to ensure items are sorted
98 | * in the order they appear in the file (top to bottom), not alphabetically.
99 | *
100 | * @return The sort text based on position.
101 | */
102 | @Override
103 | public String getSortText() {
104 | // Use position with leading zeros for correct lexicographic sorting
105 | // Max file size: 10 million characters = 8 digits
106 | return String.format("%08d", startPosition);
107 | }
108 |
109 | /**
110 | * Returns the HTML representation of the structure item, used in the
111 | * Navigator.\n Hier wird der Name in einfachem Text dargestellt.
112 | *
113 | * @param formatter The HTML formatter.
114 | * @return The HTML text.
115 | */
116 | @Override
117 | public String getHtml(HtmlFormatter formatter) {
118 | formatter.appendText(name);
119 | return formatter.getText();
120 | }
121 |
122 | /**
123 | * Returns any nested structure items. Da wir nur RequestLines anzeigen,\n
124 | * gibt es keine Verschachtelung.
125 | *
126 | * @return An empty list.
127 | */
128 | @Override
129 | public List extends StructureItem> getNestedItems() {
130 | return Collections.emptyList();
131 | }
132 |
133 | @Override
134 | public ElementHandle getElementHandle() {
135 | return this;
136 | }
137 |
138 | @Override
139 | public Set getModifiers() {
140 | return Collections.emptySet();
141 | }
142 |
143 | @Override
144 | public boolean isLeaf() {
145 | return true;
146 | }
147 |
148 | @Override
149 | public long getPosition() {
150 | return startPosition;
151 | }
152 |
153 | @Override
154 | public long getEndPosition() {
155 | return stopPosition;
156 | }
157 |
158 | @Override
159 | public ImageIcon getCustomIcon() {
160 | return new ImageIcon(ImageUtilities.loadImage("com/javierllorente/netbeans/rest/client/http/editor/http.png"));
161 | }
162 |
163 | @Override
164 | public FileObject getFileObject() {
165 | return fo;
166 | }
167 |
168 | @Override
169 | public String getMimeType() {
170 | return HTTPTokenId.MIME_TYPE;
171 | }
172 |
173 | @Override
174 | public String getIn() {
175 | return null;
176 | }
177 |
178 | @Override
179 | public boolean signatureEquals(ElementHandle eh) {
180 | return false;
181 | }
182 |
183 | @Override
184 | public OffsetRange getOffsetRange(ParserResult pr) {
185 | return new OffsetRange(startPosition, stopPosition);
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/parsers/PostmanUtilities.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2024 Javier Llorente .
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.javierllorente.netbeans.rest.client.parsers;
17 |
18 | import com.javierllorente.netbeans.rest.util.FormatUtils;
19 | import com.javierllorente.netbeans.rest.client.ui.RestClientTopComponent;
20 | import jakarta.json.Json;
21 | import jakarta.json.JsonArray;
22 | import jakarta.json.JsonArrayBuilder;
23 | import jakarta.json.JsonObject;
24 | import jakarta.json.JsonReader;
25 | import jakarta.json.JsonValue;
26 | import jakarta.ws.rs.core.MultivaluedHashMap;
27 | import jakarta.ws.rs.core.MultivaluedMap;
28 | import java.io.IOException;
29 | import java.io.StringReader;
30 | import java.nio.file.Files;
31 | import java.nio.file.Path;
32 | import java.util.List;
33 | import java.util.Map;
34 | import javax.swing.SwingUtilities;
35 | import org.openide.windows.TopComponent;
36 |
37 | /**
38 | *
39 | * @author Javier Llorente
40 | */
41 | public class PostmanUtilities {
42 |
43 | private static final String SCHEMA = "https://schema.getpostman.com/json/collection/v2.1.0/collection.json";
44 |
45 | private PostmanUtilities() {
46 | }
47 |
48 | public static int importRequests(Path path) throws IOException {
49 | int imported = 0;
50 | String json = Files.readString(path);
51 |
52 | try (JsonReader reader = Json.createReader(new StringReader(json))) {
53 |
54 | JsonObject collection = reader.readObject();
55 | JsonArray itemArray = collection.getJsonArray("item");
56 |
57 | if (itemArray != null) {
58 | for (JsonValue jsonValue : itemArray) {
59 | JsonObject itemObject = jsonValue.asJsonObject();
60 | JsonObject requestObject = itemObject.getJsonObject("request");
61 | if (requestObject != null) {
62 | JsonArray headerArray = requestObject.getJsonArray("header");
63 | MultivaluedMap headers = new MultivaluedHashMap<>();
64 |
65 | if (headerArray != null && !headerArray.isEmpty()) {
66 | for (int i = 0; i < headerArray.size(); i++) {
67 | JsonObject header = headerArray.getJsonObject(i);
68 | headers.add(header.getString("key"), header.getString("value"));
69 | }
70 | }
71 |
72 | SwingUtilities.invokeLater(() -> {
73 | RestClientTopComponent component = new RestClientTopComponent();
74 | component.open();
75 | String url = null;
76 | JsonValue urlValue = requestObject.get("url");
77 | switch (urlValue.getValueType()) {
78 | case STRING:
79 | url = requestObject.getString("url");
80 | break;
81 | case OBJECT:
82 | JsonObject urlObject = requestObject.getJsonObject("url");
83 | url = urlObject.getString("raw");
84 | break;
85 | default:
86 | throw new AssertionError(urlValue.getValueType().name());
87 | }
88 | component.setUrl(url);
89 | component.setRequestMethod(requestObject.getString("method"));
90 | // Remove User-Agent (automatically added on start-up)
91 | component.clearHeaders();
92 | component.setHeaders(headers);
93 | component.requestActive();
94 | });
95 |
96 | ++imported;
97 | }
98 | }
99 | }
100 | }
101 |
102 | return imported;
103 | }
104 |
105 | public static int exportRequests(Path path) throws IOException {
106 | int exported = 0;
107 | JsonArrayBuilder items = Json.createArrayBuilder();
108 |
109 | for (var topComponent : TopComponent.getRegistry().getOpened()) {
110 | if (topComponent instanceof RestClientTopComponent) {
111 | RestClientTopComponent restClientTopComponent = (RestClientTopComponent) topComponent;
112 |
113 | JsonArrayBuilder headers = Json.createArrayBuilder();
114 | for (Map.Entry> entry : restClientTopComponent
115 | .getHeaders().entrySet()) {
116 | headers.add(Json.createObjectBuilder()
117 | .add("key", entry.getKey())
118 | .add("value", entry.getValue().get(0))
119 | .build());
120 | }
121 |
122 | JsonObject item = Json.createObjectBuilder()
123 | .add("name", restClientTopComponent.getDisplayUrl())
124 | .add("request", Json.createObjectBuilder()
125 | .add("method", restClientTopComponent.getRequestMethod())
126 | .add("url", restClientTopComponent.getUrl())
127 | .add("header", headers.build())
128 | .build())
129 | .build();
130 | items.add(item);
131 | ++exported;
132 | }
133 | }
134 |
135 | JsonObject json = Json.createObjectBuilder()
136 | .add("info", Json.createObjectBuilder()
137 | .add("name", "NetBeans REST Client")
138 | .add("schema", SCHEMA)
139 | .build())
140 | .add("item", items.build())
141 | .build();
142 | Files.writeString(path, FormatUtils.jsonPrettyFormat(json));
143 |
144 | return exported;
145 | }
146 |
147 | }
148 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/ui/BodyPanel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022 Javier Llorente .
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.javierllorente.netbeans.rest.client.ui;
17 |
18 | import java.awt.CardLayout;
19 | import javax.swing.text.StyledEditorKit;
20 |
21 | /**
22 | *
23 | * @author Javier Llorente
24 | */
25 | public class BodyPanel extends javax.swing.JPanel {
26 |
27 | /**
28 | * Creates new form BodyPanel
29 | */
30 | public BodyPanel() {
31 | initComponents();
32 | }
33 |
34 | public String getBodyType() {
35 | return bodyComboBox.getSelectedItem().toString();
36 | }
37 |
38 | public void setBodyType(String bodyType) {
39 | bodyComboBox.setSelectedItem(bodyType);
40 | }
41 |
42 | public String getBody() {
43 | return editorPane.getText();
44 | }
45 |
46 | public void setBody(String body) {
47 | editorPane.setText(body);
48 | }
49 |
50 | public void setComboBoxEnabled(boolean enable) {
51 | bodyComboBox.setEnabled(enable);
52 | }
53 |
54 | /**
55 | * This method is called from within the constructor to initialize the form.
56 | * WARNING: Do NOT modify this code. The content of this method is always
57 | * regenerated by the Form Editor.
58 | */
59 | @SuppressWarnings("unchecked")
60 | // //GEN-BEGIN:initComponents
61 | private void initComponents() {
62 |
63 | bodyComboBox = new javax.swing.JComboBox<>();
64 | bodyTypePanel = new javax.swing.JPanel();
65 | nonePanel = new javax.swing.JPanel();
66 | rawBodyPanel = new javax.swing.JPanel();
67 | scrollPane = new javax.swing.JScrollPane();
68 | editorPane = new javax.swing.JEditorPane();
69 |
70 | setPreferredSize(new java.awt.Dimension(800, 144));
71 |
72 | bodyComboBox.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "None", "Text", "JSON", "XML" }));
73 | bodyComboBox.setEnabled(false);
74 | bodyComboBox.setPreferredSize(new java.awt.Dimension(88, 22));
75 | bodyComboBox.addActionListener(new java.awt.event.ActionListener() {
76 | public void actionPerformed(java.awt.event.ActionEvent evt) {
77 | bodyComboBoxActionPerformed(evt);
78 | }
79 | });
80 |
81 | bodyTypePanel.setPreferredSize(new java.awt.Dimension(200, 100));
82 | bodyTypePanel.setLayout(new java.awt.CardLayout());
83 |
84 | javax.swing.GroupLayout nonePanelLayout = new javax.swing.GroupLayout(nonePanel);
85 | nonePanel.setLayout(nonePanelLayout);
86 | nonePanelLayout.setHorizontalGroup(
87 | nonePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
88 | .addGap(0, 637, Short.MAX_VALUE)
89 | );
90 | nonePanelLayout.setVerticalGroup(
91 | nonePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
92 | .addGap(0, 138, Short.MAX_VALUE)
93 | );
94 |
95 | bodyTypePanel.add(nonePanel, "None");
96 |
97 | editorPane.setEditorKit(new StyledEditorKit());
98 | scrollPane.setViewportView(editorPane);
99 |
100 | javax.swing.GroupLayout rawBodyPanelLayout = new javax.swing.GroupLayout(rawBodyPanel);
101 | rawBodyPanel.setLayout(rawBodyPanelLayout);
102 | rawBodyPanelLayout.setHorizontalGroup(
103 | rawBodyPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
104 | .addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 637, Short.MAX_VALUE)
105 | );
106 | rawBodyPanelLayout.setVerticalGroup(
107 | rawBodyPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
108 | .addComponent(scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 138, Short.MAX_VALUE)
109 | );
110 |
111 | bodyTypePanel.add(rawBodyPanel, "Raw");
112 |
113 | javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
114 | this.setLayout(layout);
115 | layout.setHorizontalGroup(
116 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
117 | .addGroup(layout.createSequentialGroup()
118 | .addContainerGap()
119 | .addComponent(bodyComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
120 | .addGap(69, 69, 69)
121 | .addComponent(bodyTypePanel, javax.swing.GroupLayout.DEFAULT_SIZE, 637, Short.MAX_VALUE))
122 | );
123 | layout.setVerticalGroup(
124 | layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
125 | .addGroup(layout.createSequentialGroup()
126 | .addContainerGap()
127 | .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
128 | .addComponent(bodyTypePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
129 | .addGroup(layout.createSequentialGroup()
130 | .addComponent(bodyComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
131 | .addContainerGap(116, Short.MAX_VALUE))))
132 | );
133 | }// //GEN-END:initComponents
134 |
135 | private void bodyComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bodyComboBoxActionPerformed
136 | CardLayout cardLayout = (CardLayout) bodyTypePanel.getLayout();
137 |
138 | String selectedItem = bodyComboBox.getSelectedItem().toString();
139 |
140 | if (selectedItem.equals("Text") || selectedItem.equals("JSON") || selectedItem.equals("XML")) {
141 | selectedItem = "Raw";
142 | }
143 |
144 | cardLayout.show(bodyTypePanel, selectedItem);
145 | }//GEN-LAST:event_bodyComboBoxActionPerformed
146 |
147 |
148 | // Variables declaration - do not modify//GEN-BEGIN:variables
149 | private javax.swing.JComboBox bodyComboBox;
150 | private javax.swing.JPanel bodyTypePanel;
151 | private javax.swing.JEditorPane editorPane;
152 | private javax.swing.JPanel nonePanel;
153 | private javax.swing.JPanel rawBodyPanel;
154 | private javax.swing.JScrollPane scrollPane;
155 | // End of variables declaration//GEN-END:variables
156 | }
157 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/ui/BodyPanel.form:
--------------------------------------------------------------------------------
1 |
2 |
3 |
141 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/http/editor/syntax/coloring/HTTPLanguage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 Christian Lenz .
3 | *
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 | package com.javierllorente.netbeans.rest.client.http.editor.syntax.coloring;
22 |
23 | import com.javierllorente.netbeans.rest.client.http.editor.navigator.HTTPLangStructureScanner;
24 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.HTTPLangLexer;
25 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.HTTPLangParser;
26 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.HTTPLangParserResult;
27 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.coloring.semantic.HTTPSemanticAnalyzer;
28 | import java.util.Collection;
29 | import java.util.EnumSet;
30 | import org.netbeans.api.lexer.Language;
31 | import org.netbeans.core.spi.multiview.MultiViewElement;
32 | import org.netbeans.core.spi.multiview.text.MultiViewEditorElement;
33 | import org.netbeans.modules.csl.api.SemanticAnalyzer;
34 | import org.netbeans.modules.csl.api.StructureScanner;
35 | import org.netbeans.modules.csl.spi.DefaultLanguageConfig;
36 | import org.netbeans.modules.csl.spi.LanguageRegistration;
37 | import org.netbeans.modules.parsing.spi.Parser;
38 | import org.netbeans.spi.lexer.LanguageHierarchy;
39 | import org.netbeans.spi.lexer.Lexer;
40 | import org.netbeans.spi.lexer.LexerRestartInfo;
41 | import org.openide.awt.ActionID;
42 | import org.openide.awt.ActionReference;
43 | import org.openide.awt.ActionReferences;
44 | import org.openide.filesystems.MIMEResolver;
45 | import org.openide.util.*;
46 | import org.openide.windows.TopComponent;
47 |
48 | /**
49 | *
50 | * @author Christian Lenz
51 | */
52 | @NbBundle.Messages(
53 | "HTTPResolver=HTTP File"
54 | )
55 | @MIMEResolver.ExtensionRegistration(
56 | displayName = "#HTTPResolver",
57 | extension = {"http", "HTTP", "rest", "REST"},
58 | mimeType = HTTPLanguage.MIME_TYPE,
59 | position = 315
60 | )
61 |
62 | @ActionReferences({
63 | @ActionReference(
64 | path = "Loaders/text/x-http/Actions",
65 | id = @ActionID(category = "System", id = "org.openide.actions.OpenAction"),
66 | position = 100,
67 | separatorAfter = 200
68 | ),
69 | @ActionReference(
70 | path = "Loaders/text/x-http/Actions",
71 | id = @ActionID(category = "Edit", id = "org.openide.actions.CutAction"),
72 | position = 300
73 | ),
74 | @ActionReference(
75 | path = "Loaders/text/x-http/Actions",
76 | id = @ActionID(category = "Edit", id = "org.openide.actions.CopyAction"),
77 | position = 400
78 | ),
79 | @ActionReference(
80 | path = "Loaders/text/x-http/Actions",
81 | id = @ActionID(category = "Edit", id = "org.openide.actions.PasteAction"),
82 | position = 550,
83 | separatorAfter = 600
84 | ),
85 | @ActionReference(
86 | path = "Loaders/text/x-http/Actions",
87 | id = @ActionID(category = "Edit", id = "org.openide.actions.DeleteAction"),
88 | position = 700
89 | ),
90 | @ActionReference(
91 | path = "Loaders/text/x-http/Actions",
92 | id = @ActionID(category = "System", id = "org.openide.actions.RenameAction"),
93 | position = 800,
94 | separatorAfter = 900
95 | ),
96 | @ActionReference(
97 | path = "Loaders/text/x-http/Actions",
98 | id = @ActionID(category = "System", id = "org.openide.actions.SaveAsTemplateAction"),
99 | position = 1000,
100 | separatorAfter = 1100
101 | ),
102 | @ActionReference(
103 | path = "Loaders/text/x-http/Actions",
104 | id = @ActionID(category = "System", id = "org.openide.actions.FileSystemAction"),
105 | position = 1200,
106 | separatorAfter = 1300
107 | ),
108 | @ActionReference(
109 | path = "Loaders/text/x-http/Actions",
110 | id = @ActionID(category = "System", id = "org.openide.actions.ToolsAction"),
111 | position = 1400
112 | ),
113 | @ActionReference(
114 | path = "Loaders/text/x-http/Actions",
115 | id = @ActionID(category = "System", id = "org.openide.actions.PropertiesAction"),
116 | position = 1500
117 | )
118 | })
119 | @LanguageRegistration(mimeType = HTTPLanguage.MIME_TYPE, useMultiview = true)
120 | public class HTTPLanguage extends DefaultLanguageConfig {
121 |
122 | public static final String MIME_TYPE = "text/x-http";
123 |
124 | @Override
125 | public Language getLexerLanguage() {
126 | return LANGUAGE;
127 | }
128 |
129 | @Override
130 | public String getLineCommentPrefix() {
131 | return "#"; // NOI18N
132 | }
133 |
134 | @Override
135 | public String getDisplayName() {
136 | return "Http"; //NOI18N
137 | }
138 |
139 | @Override
140 | public SemanticAnalyzer getSemanticAnalyzer() {
141 | return new HTTPSemanticAnalyzer();
142 | }
143 |
144 | @Override
145 | public Parser getParser() {
146 | return new HTTPLangParser();
147 | }
148 |
149 | @Override
150 | public StructureScanner getStructureScanner() {
151 | return new HTTPLangStructureScanner();
152 | }
153 |
154 | @Override
155 | public boolean hasStructureScanner() {
156 | return true;
157 | }
158 |
159 | public static final Language LANGUAGE = new LanguageHierarchy() {
160 | @Override
161 | protected Collection createTokenIds() {
162 | return EnumSet.allOf(HTTPTokenId.class);
163 | }
164 |
165 | @Override
166 | protected Lexer createLexer(LexerRestartInfo info) {
167 | return new HTTPLangLexer(info);
168 | }
169 |
170 | @Override
171 | protected String mimeType() {
172 | return HTTPLanguage.MIME_TYPE;
173 | }
174 | }.language();
175 |
176 | @NbBundle.Messages("Source=&Source")
177 | @MultiViewElement.Registration(
178 | displayName = "#Source",
179 | persistenceType = TopComponent.PERSISTENCE_ONLY_OPENED,
180 | mimeType = HTTPLanguage.MIME_TYPE,
181 | preferredID = "http.source",
182 | position = 100
183 | )
184 | public static MultiViewEditorElement createMultiViewEditorElement(Lookup context) {
185 | return new MultiViewEditorElement(context);
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/main/antlr4/HTTPParser.g4:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 Christian Lenz .
3 | *
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 |
22 | /**
23 | *
24 | * @author Christian Lenz
25 | */
26 | parser grammar HTTPParser;
27 |
28 | options {
29 | tokenVocab = HTTPLexer;
30 | }
31 |
32 | httpRequestsFile:
33 | (blank | COMMENT)*
34 | requestBlock?
35 | ((blank | COMMENT)* REQUEST_SEPARATOR (blank | COMMENT)* requestBlock?)*
36 | (blank | COMMENT)*
37 | EOF;
38 |
39 | requestBlock: blank* (request | invalidBodyWithoutRequest);
40 |
41 | request:
42 | requestLine requestHeaders? requestBodySection?
43 | | requestLineWithBody;
44 |
45 | requestHeaders: (headerLine)+;
46 |
47 | headerLine:
48 | (WS* header (NEWLINE | {_input.LA(1) == BODY_START_WITH_BLANK || _input.LA(1) == GENERIC_BODY_START || _input.LA(1) == BODY_START_NO_BLANK || _input.LA(1) == EOF}?)
49 | | WS* invalidHeaderLine);
50 |
51 | invalidBodyWithoutRequest:
52 | {
53 | notifyErrorListeners("Request body requires a request line");
54 | }
55 | (BODY_START_WITH_BLANK | BODY_START_NO_BLANK | GENERIC_BODY_START)
56 | .*?
57 | (REQUEST_SEPARATOR | EOF);
58 |
59 | invalidHeaderLine:
60 | {_input.LA(2) != COLON && _input.LA(1) != COMMENT && _input.LA(1) != NEWLINE}?
61 | invalidHeaderContent
62 | {
63 | notifyErrorListeners("Unknown HTTP header");
64 | }
65 | NEWLINE?;
66 |
67 | invalidHeaderContent: ~(NEWLINE | REQUEST_SEPARATOR | BODY_START_WITH_BLANK | BODY_START_NO_BLANK | GENERIC_BODY_START | COMMENT | EOF | WS)+;
68 |
69 | requestBodySection:
70 | (WS | NEWLINE | COMMENT)* requestBody;
71 |
72 | requestBody:
73 | bodyWithStarter
74 | | directBodyContent;
75 |
76 | bodyWithStarter:
77 | (BODY_START_WITH_BLANK | BODY_START_NO_BLANK | GENERIC_BODY_START) bodyContent;
78 |
79 | // Pure body content in BODY_CONTENT mode
80 | bodyContent:
81 | (.)*? CLOSE_BLOCK_BRAKET // Match anything until }
82 | | (.)* ; // Match anything until end
83 |
84 | // Direct body content for special cases (uppercase, etc.)
85 | directBodyContent:
86 | (~(REQUEST_SEPARATOR | EOF))+ ;
87 |
88 | // Json structure for requestBody
89 | jsonObject:
90 | OPEN_BLOCK_BRAKET space (pair (space COMMA space pair)*)? space
91 | (CLOSE_BLOCK_BRAKET | { notifyErrorListeners("Missing closing bracket '}' in JSON object"); });
92 |
93 | pair:
94 | jsonString space COLON space jsonValue;
95 |
96 | jsonArray:
97 | OPEN_BRAKET space (jsonValue (space COMMA space jsonValue)*)? space
98 | (CLOSE_BRAKET | { notifyErrorListeners("Missing closing bracket ']' in JSON array"); });
99 |
100 | jsonValue:
101 | jsonString
102 | | jsonNumber
103 | | jsonObject
104 | | jsonArray
105 | | jsonBareWord;
106 |
107 | jsonString: STRING | QUOTE jsonStringContent? QUOTE;
108 |
109 | jsonStringContent: jsonStringChar+;
110 |
111 | jsonStringChar:
112 | ALPHA_CHARS
113 | | DIGITS
114 | | DASH
115 | | UNDERSCORE
116 | | DOT
117 | | SLASH
118 | | COLON
119 | | AMPERSAND
120 | | PERCENT
121 | | QUESTION_MARK
122 | | HASH
123 | | WS
124 | | OPEN_BRAKET
125 | | CLOSE_BRAKET
126 | | OPEN_BLOCK_BRAKET
127 | | CLOSE_BLOCK_BRAKET
128 | | ASTERISK
129 | | EQUAL
130 | | SCHEME_SEPARATOR
131 | | COMMA;
132 |
133 | jsonNumber: NUMBER;
134 |
135 | jsonBareWord: ALPHA_CHARS;
136 |
137 | space: (NEWLINE | WS)*;
138 |
139 | header: headerField;
140 |
141 | headerField: headerFieldName COLON WS* headerFieldValue;
142 |
143 | headerFieldName: (ALPHA_CHARS | DASH | UNDERSCORE)+;
144 | headerFieldValue: (~(NEWLINE | BODY_START_WITH_BLANK | BODY_START_NO_BLANK | GENERIC_BODY_START))*;
145 |
146 | fieldName: QUOTE (ALPHA_CHARS | DASH | UNDERSCORE) QUOTE;
147 |
148 | fieldValue: QUOTE? (ALPHA_CHARS | DASH | UNDERSCORE | DIGITS | SLASH)+ QUOTE?;
149 |
150 | blank: NEWLINE | WS;
151 |
152 | requestLine:
153 | (METHOD WS+)? requestTarget (WS+ httpVersion)? WS* NEWLINE?;
154 |
155 | requestLineWithBody:
156 | (METHOD WS+)? requestTarget (WS+ httpVersion)? BODY_START_WITH_BLANK bodyContent;
157 |
158 | requestTarget: originForm | absoluteForm | asteriskForm;
159 |
160 | originForm: slashPathPart (queryPart | fragmentPart)*;
161 |
162 | absolutePath: SLASH (pathSeparator segment)+;
163 |
164 | segment: (ALPHA_CHARS | DIGITS | DASH | UNDERSCORE)+;
165 |
166 | pathSeparator: SLASH WS+;
167 |
168 | absoluteForm: (SCHEME SCHEME_SEPARATOR)? (hostPort | ipAddress) (
169 | slashPathPart
170 | | queryPart
171 | | fragmentPart
172 | )*;
173 |
174 | hostPort: host (COLON DIGITS)*;
175 |
176 | host: segment (DOT segment)*;
177 |
178 | ipAddress: ipv4Address | ipv6Address;
179 |
180 | ipv4Address:
181 | DIGITS DOT DIGITS DOT DIGITS DOT DIGITS (COLON DIGITS)*;
182 |
183 | ipv6Address:
184 | OPEN_BRAKET ipv6Literal CLOSE_BRAKET (COLON DIGITS)*;
185 |
186 | ipv6Literal: fullIPv6 | compressedIPv6;
187 |
188 | fullIPv6:
189 | hextet COLON hextet COLON hextet COLON hextet COLON hextet COLON hextet COLON hextet COLON
190 | hextet;
191 |
192 | compressedIPv6: (hextet (COLON hextet)*)? COLON COLON (
193 | hextet (COLON hextet)*
194 | )?;
195 |
196 | hextet: hexa+;
197 |
198 | hexa: (DIGITS | ALPHA_CHARS)+;
199 |
200 | slashPathPart: SLASH (pathSegment (SLASH pathSegment)* SLASH?)?;
201 |
202 | pathSegment: (ALPHA_CHARS | DIGITS) (DOT | DASH | UNDERSCORE | ALPHA_CHARS | DIGITS)*;
203 |
204 | asteriskForm: ASTERISK;
205 |
206 | queryPart: QUESTION_MARK queryContent?;
207 |
208 | queryContent:
209 | queryParam (AMPERSAND queryParam)*;
210 |
211 | queryParam:
212 | queryKey (EQUAL queryValue)?;
213 |
214 | queryKey:
215 | (ALPHA_CHARS | DIGITS | DASH | UNDERSCORE | DOT | PERCENT)+;
216 |
217 | queryValue:
218 | (ALPHA_CHARS
219 | | DIGITS
220 | | DOT
221 | | SLASH
222 | | COLON
223 | | DASH
224 | | UNDERSCORE
225 | | PERCENT
226 | | EQUAL
227 | | QUESTION_MARK
228 | | HASH
229 | | OPEN_BRAKET
230 | | CLOSE_BRAKET
231 | | SCHEME_SEPARATOR
232 | | SCHEME
233 | | COMMA
234 | )+;
235 |
236 | fragmentPart: HASH fragmentContent;
237 |
238 | fragmentContent: (
239 | ALPHA_CHARS
240 | | DIGITS
241 | | DOT
242 | | SLASH
243 | | COLON
244 | | PERCENT
245 | )+;
246 |
247 | httpVersion:
248 | HTTP_PROTOCOL
249 | (SLASH | { notifyErrorListeners("Missing '/' in HTTP version"); })
250 | (versionNumber | { notifyErrorListeners("Invalid HTTP version number"); }); // HTTP/1.1
251 |
252 | versionNumber: DIGITS (DOT DIGITS)?;
253 |
254 | invalidContent: ~(WS | NEWLINE | REQUEST_SEPARATOR | METHOD)+;
255 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/DrawingPanel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 Christian Lenz .
3 | *
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar;
22 |
23 | import com.javierllorente.netbeans.rest.client.http.editor.sidebar.request.IRequestProcessor;
24 | import com.javierllorente.netbeans.rest.client.http.editor.sidebar.request.Request;
25 | import java.awt.Color;
26 | import java.awt.Cursor;
27 | import java.awt.Dimension;
28 | import java.awt.Graphics;
29 | import java.awt.Graphics2D;
30 | import java.awt.Rectangle;
31 | import java.awt.RenderingHints;
32 | import java.awt.geom.Rectangle2D;
33 | import java.util.ArrayList;
34 | import java.util.List;
35 | import javax.swing.JPanel;
36 | import javax.swing.event.DocumentEvent;
37 | import javax.swing.event.DocumentListener;
38 | import javax.swing.text.BadLocationException;
39 | import javax.swing.text.JTextComponent;
40 | import javax.swing.text.StyledDocument;
41 | import org.netbeans.editor.Utilities;
42 | import org.openide.text.NbDocument;
43 | import org.openide.util.Exceptions;
44 |
45 | /**
46 | *
47 | * @author Chrizzly
48 | */
49 | public class DrawingPanel extends JPanel {
50 |
51 | private final JTextComponent textComponent;
52 | private final StyledDocument document;
53 | private final List taskAreas;
54 | private final IRequestProcessor taskProcessor; // Nur ein TaskProcessor
55 | private final ActionPopup actionPopup;
56 |
57 | public DrawingPanel(JTextComponent editor, IRequestProcessor processor) { // Nur ein TaskProcessor
58 | this.textComponent = editor;
59 | this.document = (StyledDocument) editor.getDocument();
60 | this.taskAreas = new ArrayList<>();
61 | this.taskProcessor = processor; // Setze den übergebenen TaskProcessor
62 | this.actionPopup = new ActionPopup(taskProcessor);
63 |
64 | document.addDocumentListener(new DocumentListener() {
65 | @Override
66 | public void insertUpdate(DocumentEvent e) {
67 | updateSidebar();
68 | }
69 |
70 | @Override
71 | public void removeUpdate(DocumentEvent e) {
72 | updateSidebar();
73 | }
74 |
75 | @Override
76 | public void changedUpdate(DocumentEvent e) {
77 | updateSidebar();
78 | }
79 | });
80 |
81 | this.setPreferredSize(new Dimension(20, HEIGHT));
82 | try {
83 | // Initialisiere Request-Liste vom TaskProcessor
84 | taskProcessor.updateRequestsList(document.getText(0, document.getLength()));
85 | } catch (BadLocationException ex) {
86 | Exceptions.printStackTrace(ex);
87 | }
88 |
89 | addMouseListener(new PopupListener());
90 | addMouseMotionListener(new CursorListener());
91 | }
92 |
93 | @Override
94 | protected void paintComponent(Graphics g) {
95 | super.paintComponent(g);
96 | Utilities.runViewHierarchyTransaction(textComponent, true, () -> paintComponentUnderLock(g));
97 | }
98 |
99 | private void paintComponentUnderLock(Graphics g) {
100 | Rectangle clip = g.getClipBounds();
101 | g.setColor(textComponent.getBackground());
102 |
103 | if (clip != null) {
104 | g.fillRect(clip.x, clip.y, clip.width, clip.height);
105 | }
106 |
107 | if (taskProcessor == null) {
108 | return;
109 | }
110 |
111 | try {
112 | drawPanel(textComponent, g);
113 | } catch (BadLocationException ex) {
114 | ex.printStackTrace();
115 | }
116 | }
117 |
118 | private void drawPanel(JTextComponent component, Graphics g) throws BadLocationException {
119 | Graphics2D g2 = (Graphics2D) g;
120 | g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
121 | g2.setColor(new Color(0, 170, 0));
122 |
123 | taskAreas.clear();
124 | List tasks = taskProcessor.getRequests(); // Hol die Tasks vom TaskProcessor
125 | tasks.sort((t1, t2) -> Integer.compare(t1.getLineNumber(), t2.getLineNumber()));
126 |
127 | for (Request task : tasks) {
128 | int lineNumber = task.getLineNumber();
129 | int startOffset = NbDocument.findLineOffset(document, lineNumber);
130 | Rectangle2D rect = component.modelToView2D(startOffset);
131 |
132 | if (rect != null) {
133 | int[] xPoints = {4, 10, 4};
134 | int[] yPoints = {(int) rect.getY(), (int) rect.getY() + 7, (int) rect.getY() + 14};
135 | g2.fillPolygon(xPoints, yPoints, 3);
136 | taskAreas.add(new Rectangle2D.Float(4, (float) rect.getY(), 6, 14));
137 | }
138 | }
139 | }
140 |
141 | private void updateSidebar() {
142 | try {
143 | String text = document.getText(0, document.getLength());
144 | updateSidebar(text);
145 | } catch (BadLocationException ex) {
146 | ex.printStackTrace();
147 | }
148 | }
149 |
150 | private void updateSidebar(String text) {
151 | taskProcessor.updateRequestsList(text);
152 | revalidate();
153 | repaint();
154 | }
155 |
156 | private class PopupListener extends java.awt.event.MouseAdapter {
157 |
158 | @Override
159 | public void mousePressed(java.awt.event.MouseEvent e) {
160 | if (e.getButton() == java.awt.event.MouseEvent.BUTTON1) {
161 | showPopup(e);
162 | }
163 | }
164 |
165 | private void showPopup(java.awt.event.MouseEvent e) {
166 | for (int i = 0; i < taskProcessor.getRequests().size(); i++) {
167 | Request task = taskProcessor.getRequests().get(i);
168 | Rectangle2D rect = taskAreas.get(i);
169 |
170 | if (rect.contains(e.getPoint())) {
171 | javax.swing.JPopupMenu popupMenu = actionPopup.createPopupMenu(task);
172 | popupMenu.show(e.getComponent(), e.getX(), e.getY());
173 | break;
174 | }
175 | }
176 | }
177 | }
178 |
179 | private class CursorListener extends java.awt.event.MouseAdapter {
180 |
181 | @Override
182 | public void mouseMoved(java.awt.event.MouseEvent e) {
183 | for (Rectangle2D rect : taskAreas) {
184 | if (rect.contains(e.getPoint())) {
185 | setCursor(Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR));
186 | return;
187 | }
188 | }
189 | setCursor(Cursor.getDefaultCursor());
190 | }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/ui/TablePanel.form:
--------------------------------------------------------------------------------
1 |
2 |
3 |
143 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/parsers/Parser.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one
3 | * or more contributor license agreements. See the NOTICE file
4 | * distributed with this work for additional information
5 | * regarding copyright ownership. The ASF licenses this file
6 | * to you under the Apache License, Version 2.0 (the
7 | * "License"); you may not use this file except in compliance
8 | * with the License. You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing,
13 | * software distributed under the License is distributed on an
14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | * KIND, either express or implied. See the License for the
16 | * specific language governing permissions and limitations
17 | * under the License.
18 | */
19 | package com.javierllorente.netbeans.rest.client.parsers;
20 |
21 | import java.util.LinkedList;
22 | import java.util.List;
23 | import java.util.regex.Matcher;
24 | import java.util.regex.Pattern;
25 |
26 | /**
27 | *
28 | * @author Jan Lahoda
29 | */
30 | public final class Parser {
31 |
32 | private enum STATE {
33 | START, H, HT, F, HTT_FT,
34 | HTTP_FTP,
35 | HTTPS,
36 | HTTPC, // // (ht|f)tp(s?)
37 | HTTPCS, // (ht|f)tp(s?):/
38 | FI,
39 | FIL,
40 | FILE,
41 | N, NB, NBF_F, NBFS,
42 | END // (ht|f)tp(s?)://
43 | }
44 |
45 | private Parser() {}
46 |
47 | public static Iterable recognizeURLs(CharSequence text) {
48 | List result = new LinkedList();
49 | STATE state = STATE.START;
50 | int lastURLStart = -1;
51 |
52 | OUTER: for (int cntr = 0; cntr < text.length(); cntr++) {
53 | char ch = text.charAt(cntr);
54 |
55 | if (state == STATE.END) {
56 | if (Character.isLetterOrDigit(ch)) {
57 | continue OUTER;
58 | }
59 |
60 | switch (ch) {
61 | case '/': case '.': case '?': case '+': //NOI18N
62 | case '%': case '_': case '~': case '=': //NOI18N
63 | case '\\':case '&': case '$': case '-': //NOI18N
64 | case '#': case ',': case ':': case ';': //NOI18N
65 | case '!': case '(': case ')': case '@': //NOI18N
66 | continue OUTER;
67 | }
68 |
69 | assert lastURLStart != (-1);
70 | result.add(new int[] {lastURLStart, cntr});
71 |
72 | lastURLStart = (-1);
73 | state = STATE.START;
74 | continue OUTER;
75 | }
76 |
77 | switch (ch) {
78 | case 'h': //NOI18N
79 | if (state == STATE.START) {
80 | lastURLStart = cntr;
81 | state = STATE.H;
82 | continue OUTER;
83 | }
84 | break;
85 | case 'n': //NOI18N
86 | if (state == STATE.START) {
87 | lastURLStart = cntr;
88 | state = STATE.N;
89 | continue OUTER;
90 | }
91 | break;
92 | case 't': //NOI18N
93 | if (state == STATE.H) {
94 | state = STATE.HT;
95 | continue OUTER;
96 | } else if (state == STATE.HT) {
97 | state = STATE.HTT_FT;
98 | continue OUTER;
99 | } else if (state == STATE.F) {
100 | state = STATE.HTT_FT;
101 | continue OUTER;
102 | }
103 | break;
104 | case 'b': //NOI18N
105 | if (state == STATE.N) {
106 | state = STATE.NB;
107 | continue OUTER;
108 | }
109 | break;
110 | case 'f': //NOI18N
111 | if (state == STATE.START) {
112 | lastURLStart = cntr;
113 | state = STATE.F;
114 | continue OUTER;
115 | } else if (state == STATE.NB) {
116 | state = STATE.NBF_F;
117 | continue OUTER;
118 | }
119 | break;
120 | case 'i': //NOI18N
121 | if (state == STATE.F || state == STATE.NBF_F) {
122 | state = STATE.FI;
123 | continue OUTER;
124 | }
125 | case 'l': //NOI18N
126 | if (state == STATE.FI) {
127 | state = STATE.FIL;
128 | continue OUTER;
129 | }
130 | case 'e': //NOI18N
131 | if (state == STATE.FIL) {
132 | state = STATE.FILE;
133 | continue OUTER;
134 | }
135 | case 'p': //NOI18N
136 | if (state == STATE.HTT_FT) {
137 | state = STATE.HTTP_FTP;
138 | continue OUTER;
139 | }
140 | break;
141 | case 's': //NOI18N
142 | if (state == STATE.HTTP_FTP) {
143 | state = STATE.HTTPS;
144 | continue OUTER;
145 | } else if (state == STATE.NBF_F) {
146 | state = STATE.NBFS;
147 | continue OUTER;
148 | }
149 | break;
150 | case ':': //NOI18N
151 | if (state == STATE.HTTP_FTP || state == STATE.HTTPS || state == STATE.FILE || state == STATE.NBFS) {
152 | state = STATE.HTTPC;
153 | continue OUTER;
154 | }
155 | break;
156 | case '/' : //NOI18N
157 | if (state == STATE.HTTPC) {
158 | state = STATE.HTTPCS;
159 | continue OUTER;
160 | } else if (state == STATE.HTTPCS) {
161 | state = STATE.END;
162 | continue OUTER;
163 | } else if (state == STATE.NBFS) {
164 | state = STATE.END;
165 | continue OUTER;
166 | }
167 | break;
168 | }
169 |
170 | state = STATE.START;
171 | lastURLStart = (-1);
172 | }
173 |
174 | if (lastURLStart != (-1) && state == STATE.END) {
175 | result.add(new int[] {lastURLStart, text.length()});
176 | }
177 |
178 | return result;
179 | }
180 |
181 | private static final Pattern URL_PATTERN = Pattern.compile("(ht|f|n)(tp(s?)|ile|bfs)://[0-9a-zA-Z/.?%+_~=\\\\&@$\\-#,:!/(/)]*"); //NOI18N
182 |
183 | public static Iterable recognizeURLsREBased(CharSequence text) {
184 | Matcher m = URL_PATTERN.matcher(text);
185 | List result = new LinkedList();
186 |
187 | while (m.find()) {
188 | result.add(new int[] {m.start(), m.start() + m.group(0).length()});
189 | }
190 |
191 | return result;
192 | }
193 |
194 | private static final Pattern CHARSET = Pattern.compile("charset=([^;]+)(;|$)", Pattern.MULTILINE);//NOI18N
195 | public static String decodeContentType(String contentType) {
196 | if (contentType == null) return null;
197 |
198 | if (contentType != null) {
199 | Matcher m = CHARSET.matcher(contentType);
200 |
201 | if (m.find()) {
202 | return m.group(1);
203 | }
204 | }
205 |
206 | return null;
207 | }
208 |
209 | }
210 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/ui/RestClientTopComponent.form:
--------------------------------------------------------------------------------
1 |
2 |
3 |
154 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/ResponseSidebarPanel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 Christian Lenz .
3 | *
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar;
22 |
23 | import com.javierllorente.netbeans.rest.client.ui.ResponsePanel;
24 | import jakarta.ws.rs.core.MultivaluedMap;
25 | import java.awt.BorderLayout;
26 | import java.awt.Color;
27 | import java.awt.Cursor;
28 | import java.awt.Dimension;
29 | import java.awt.FlowLayout;
30 | import java.awt.Font;
31 | import java.awt.event.MouseAdapter;
32 | import java.awt.event.MouseEvent;
33 | import java.util.List;
34 | import java.util.Map;
35 | import javax.swing.BorderFactory;
36 | import javax.swing.JButton;
37 | import javax.swing.JPanel;
38 | import javax.swing.SwingUtilities;
39 | import javax.swing.text.JTextComponent;
40 |
41 | /**
42 | * Sidebar panel for displaying HTTP response content.
43 | *
44 | * @author Christian Lenz
45 | */
46 | public class ResponseSidebarPanel extends JPanel {
47 |
48 | private static final int MIN_WIDTH = 120;
49 | private static final int MAX_WIDTH = 1200;
50 |
51 | private final ResponsePanel responsePanel;
52 | private final JTextComponent textComponent;
53 |
54 | private boolean visible;
55 | private int calculatedWidth = -1; // Cache the width once calculated
56 | private int pressXScreen;
57 | private int startWidth;
58 |
59 | public ResponseSidebarPanel(JTextComponent target) {
60 | super(new BorderLayout());
61 | this.textComponent = target;
62 | this.visible = false;
63 |
64 | // Create header panel with close button
65 | JPanel headerPanel = new JPanel(new BorderLayout());
66 | headerPanel.setBackground(new Color(240, 240, 240));
67 | headerPanel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
68 |
69 | // Title label
70 | JPanel titlePanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0));
71 | titlePanel.setBackground(new Color(240, 240, 240));
72 | javax.swing.JLabel titleLabel = new javax.swing.JLabel("Response");
73 | titleLabel.setFont(titleLabel.getFont().deriveFont(Font.BOLD));
74 | titlePanel.add(titleLabel);
75 |
76 | // Close button (simple X)
77 | JButton closeButton = new JButton("\u00D7");
78 | closeButton.setFont(closeButton.getFont().deriveFont(Font.BOLD, 16f));
79 | closeButton.setPreferredSize(new Dimension(20, 20));
80 | closeButton.setBorderPainted(false);
81 | closeButton.setContentAreaFilled(false);
82 | closeButton.setFocusPainted(false);
83 | closeButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
84 | closeButton.setToolTipText("Close response view");
85 | closeButton.addActionListener(e -> {
86 | setVisible(false);
87 | revalidate();
88 | repaint();
89 | });
90 |
91 | headerPanel.add(titlePanel, BorderLayout.WEST);
92 | headerPanel.add(closeButton, BorderLayout.EAST);
93 |
94 | // Use the ResponsePanel component which already has all the functionality
95 | responsePanel = new ResponsePanel();
96 |
97 | add(headerPanel, BorderLayout.NORTH);
98 | add(responsePanel, BorderLayout.CENTER);
99 |
100 | JPanel resizablePanel = createResizeHandle();
101 | add(resizablePanel, BorderLayout.WEST);
102 |
103 | setPreferredSize(new Dimension(0, 0));
104 | }
105 |
106 | private JPanel createResizeHandle() {
107 | JPanel handle = new JPanel();
108 | handle.setOpaque(true);
109 | handle.setPreferredSize(new Dimension(10, 0));
110 | handle.setCursor(Cursor.getPredefinedCursor(Cursor.W_RESIZE_CURSOR));
111 |
112 | MouseAdapter ma = new MouseAdapter() {
113 | @Override
114 | public void mousePressed(MouseEvent e) {
115 | pressXScreen = e.getXOnScreen();
116 | startWidth = getPreferredSize().width;
117 | }
118 |
119 | @Override
120 | public void mouseDragged(MouseEvent e) {
121 | int delta = e.getXOnScreen() - pressXScreen;
122 | calculatedWidth = clamp(startWidth - delta, MIN_WIDTH, MAX_WIDTH);
123 | setPreferredSize(new Dimension(calculatedWidth, 0));
124 | revalidate();
125 |
126 | if (getParent() != null) {
127 | getParent().revalidate();
128 | }
129 | }
130 | };
131 |
132 | handle.addMouseListener(ma);
133 | handle.addMouseMotionListener(ma);
134 |
135 | return handle;
136 | }
137 |
138 | private static int clamp(int v, int min, int max) {
139 | return Math.max(min, Math.min(max, v));
140 | }
141 |
142 | /**
143 | * Set response headers for display.
144 | */
145 | public void setResponseHeaders(MultivaluedMap headers) {
146 | if (headers != null) {
147 | for (Map.Entry> entry : headers.entrySet()) {
148 | String key = entry.getKey();
149 | String val = entry.getValue().toString();
150 | responsePanel.addHeader(key, val);
151 | }
152 | }
153 | }
154 |
155 | /**
156 | * Set the response content (raw).
157 | */
158 | public void setResponse(String response) {
159 | responsePanel.setResponse(response);
160 | }
161 |
162 | @Override
163 | public void setVisible(boolean visible) {
164 | try {
165 | this.visible = visible;
166 | if (visible) {
167 | // Calculate width only once when first shown, or use cached value
168 | if (calculatedWidth == -1) {
169 | // Use invokeLater to ensure component is fully laid out
170 | SwingUtilities.invokeLater(() -> {
171 | int editorWidth = textComponent != null && textComponent.getParent() != null
172 | ? textComponent.getParent().getWidth() : 800;
173 | calculatedWidth = editorWidth / 2; // Always 50%
174 | setPreferredSize(new Dimension(calculatedWidth, 0));
175 | setSize(new Dimension(calculatedWidth, getHeight()));
176 | if (getParent() != null) {
177 | getParent().revalidate();
178 | getParent().repaint();
179 | }
180 | });
181 | } else {
182 | // Use cached width for consistent sizing
183 | setPreferredSize(new Dimension(calculatedWidth, 0));
184 | setSize(new Dimension(calculatedWidth, getHeight()));
185 | }
186 | } else {
187 | setPreferredSize(new Dimension(0, 0));
188 | }
189 |
190 | super.setVisible(visible);
191 |
192 | // Force parent container to relayout
193 | if (getParent() != null) {
194 | getParent().revalidate();
195 | getParent().repaint();
196 | }
197 | } catch (Exception e) {
198 | System.err.println("Error setting sidebar visibility: " + e.getMessage());
199 | }
200 | }
201 |
202 | @Override
203 | public boolean isVisible() {
204 | return visible;
205 | }
206 |
207 | /**
208 | * Show the sidebar with the given response and headers.
209 | */
210 | public void showResponse(String response, String contentType, MultivaluedMap headers) {
211 | try {
212 | responsePanel.clear();
213 | responsePanel.setContentType(contentType);
214 | setResponse(response);
215 | setResponseHeaders(headers);
216 | responsePanel.showResponse();
217 | setVisible(true);
218 | revalidate();
219 | repaint();
220 | } catch (Exception e) {
221 | System.err.println("Error showing response: " + e.getMessage());
222 | e.printStackTrace();
223 | }
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/ui/ResponsePanel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2022-2025 Javier Llorente .
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.javierllorente.netbeans.rest.client.ui;
17 |
18 | import com.javierllorente.netbeans.rest.util.FormatUtils;
19 | import com.javierllorente.netbeans.rest.client.editor.RestMediaType;
20 | import jakarta.json.Json;
21 | import jakarta.json.JsonReader;
22 | import jakarta.ws.rs.core.MediaType;
23 | import java.awt.BorderLayout;
24 | import java.awt.Desktop;
25 | import java.awt.Dimension;
26 | import java.awt.FlowLayout;
27 | import java.io.File;
28 | import java.io.IOException;
29 | import java.io.StringReader;
30 | import java.nio.file.Files;
31 | import javax.swing.ButtonGroup;
32 | import javax.swing.JButton;
33 | import javax.swing.JComponent;
34 | import javax.swing.JEditorPane;
35 | import javax.swing.JLayer;
36 | import javax.swing.JOptionPane;
37 | import javax.swing.JPanel;
38 | import javax.swing.JScrollPane;
39 | import javax.swing.JTabbedPane;
40 | import javax.swing.JToggleButton;
41 | import org.openide.text.CloneableEditorSupport;
42 |
43 | /**
44 | *
45 | * @author Javier Llorente
46 | */
47 | public class ResponsePanel extends JPanel {
48 |
49 | private final JTabbedPane responseTabbedPane;
50 | private final JEditorPane responseEditorPane;
51 | private final JToggleButton prettyButton;
52 | private final TablePanel responseHeadersTable;
53 | private final StatusLabel statusLabel;
54 | private final LineNumberComponent lineNumberComponent;
55 | private final JButton previewButton;
56 | private String mimePath = MediaType.TEXT_PLAIN;
57 | private String response;
58 |
59 | public ResponsePanel() {
60 | super(new BorderLayout());
61 |
62 | responseTabbedPane = new JTabbedPane();
63 | responseTabbedPane.setPreferredSize(new Dimension(705, 150));
64 |
65 | responseEditorPane = new JEditorPane();
66 | responseEditorPane.setEditable(false);
67 | responseEditorPane.setCursor(new java.awt.Cursor(java.awt.Cursor.TEXT_CURSOR));
68 | responseEditorPane.setAutoscrolls(true);
69 | JScrollPane responseScrollPane = new JScrollPane();
70 | responseScrollPane.setViewportView(responseEditorPane);
71 |
72 | JPanel topPanel = new JPanel();
73 | topPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
74 |
75 | prettyButton = new JToggleButton("Pretty", true);
76 | JToggleButton rawButton = new JToggleButton("Raw", false);
77 | prettyButton.addItemListener((ie) -> {
78 | showResponse();
79 | });
80 |
81 | ButtonGroup formatGroup = new ButtonGroup();
82 | formatGroup.add(prettyButton);
83 | formatGroup.add(rawButton);
84 | topPanel.add(prettyButton);
85 | topPanel.add(rawButton);
86 |
87 | previewButton = new JButton("Preview");
88 | previewButton.setToolTipText("Open HTML response in browser");
89 | previewButton.setVisible(false); // Initially hidden, shown only for HTML responses
90 | previewButton.addActionListener((ae) -> {
91 | renderHtml();
92 | });
93 | topPanel.add(previewButton);
94 |
95 | responseScrollPane = new JScrollPane();
96 |
97 | JPanel responseBodyPanel = new JPanel();
98 | responseBodyPanel.setLayout(new BorderLayout());
99 | responseBodyPanel.add(topPanel, BorderLayout.NORTH);
100 | responseBodyPanel.add(responseScrollPane, BorderLayout.CENTER);
101 |
102 | responseEditorPane.setEditorKit(CloneableEditorSupport.getEditorKit(mimePath));
103 | responseScrollPane.setViewportView(responseEditorPane);
104 |
105 | responseTabbedPane.addTab("Body", responseBodyPanel);
106 | responseHeadersTable = new TablePanel();
107 | responseHeadersTable.setReadOnly();
108 | responseTabbedPane.addTab("Headers", responseHeadersTable);
109 |
110 | statusLabel = new StatusLabel();
111 | JLayer decoratedPane = new JLayer<>(responseTabbedPane, statusLabel);
112 | add(decoratedPane);
113 |
114 | lineNumberComponent = new LineNumberComponent(responseEditorPane);
115 |
116 | responseScrollPane.getVerticalScrollBar().addAdjustmentListener(e -> lineNumberComponent.repaint());
117 | responseScrollPane.getHorizontalScrollBar().addAdjustmentListener(e -> lineNumberComponent.repaint());
118 | responseScrollPane.setRowHeaderView(lineNumberComponent);
119 |
120 | }
121 |
122 | public void setContentType(String contentType) {
123 | mimePath = MediaType.TEXT_PLAIN;
124 | if (contentType.startsWith(MediaType.APPLICATION_XML)) {
125 | mimePath = RestMediaType.XML;
126 | } else if (contentType.startsWith(MediaType.APPLICATION_JSON)
127 | || contentType.startsWith("application/javascript")) {
128 | mimePath = RestMediaType.JSON;
129 | } else if (contentType.startsWith(MediaType.TEXT_HTML)) {
130 | mimePath = MediaType.TEXT_HTML;
131 | }
132 | responseEditorPane.setEditorKit(CloneableEditorSupport.getEditorKit(mimePath));
133 |
134 | // Show Preview button only for HTML responses
135 | previewButton.setVisible(mimePath.equals(MediaType.TEXT_HTML));
136 | }
137 |
138 | public void setResponse(String response) {
139 | this.response = response;
140 | }
141 |
142 | private String formatResponse() {
143 | String prettyOrNotResponse = response;
144 | if (prettyButton.isSelected()) {
145 | switch (mimePath) {
146 | case RestMediaType.JSON:
147 | try (JsonReader jsonReader = Json.createReader(new StringReader(prettyOrNotResponse))) {
148 | prettyOrNotResponse = FormatUtils.jsonPrettyFormat(jsonReader.read());
149 | }
150 | break;
151 | case RestMediaType.XML:
152 | prettyOrNotResponse = FormatUtils.xmlPrettyFormat(prettyOrNotResponse);
153 | break;
154 | case MediaType.TEXT_HTML:
155 | prettyOrNotResponse = FormatUtils.htmlPrettyFormat(prettyOrNotResponse);
156 | break;
157 | }
158 | }
159 |
160 | lineNumberComponent.repaint();
161 |
162 | return prettyOrNotResponse;
163 | }
164 |
165 | public void showResponse() {
166 | String prettyOrNotResponse = formatResponse();
167 | responseEditorPane.setText(prettyOrNotResponse);
168 | responseEditorPane.setCaretPosition(0);
169 | }
170 |
171 | /**
172 | * Render HTML response in system browser.
173 | */
174 | private void renderHtml() {
175 | if (response == null || response.isEmpty()) {
176 | JOptionPane.showMessageDialog(this, "No HTML content to render", "Info", JOptionPane.INFORMATION_MESSAGE);
177 | return;
178 | }
179 |
180 | try {
181 | // Create temporary HTML file
182 | File tempFile = File.createTempFile("rest-response-", ".html");
183 | tempFile.deleteOnExit();
184 | Files.writeString(tempFile.toPath(), response);
185 |
186 | // Open in default browser
187 | if (Desktop.isDesktopSupported()) {
188 | Desktop.getDesktop().browse(tempFile.toURI());
189 | } else {
190 | JOptionPane.showMessageDialog(this, "Desktop not supported - cannot open browser", "Error", JOptionPane.ERROR_MESSAGE);
191 | }
192 | } catch (IOException ex) {
193 | JOptionPane.showMessageDialog(this, "Failed to render HTML: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
194 | }
195 | }
196 |
197 | public void clearResponse() {
198 | responseEditorPane.setText("");
199 | responseEditorPane.setCaretPosition(0);
200 | }
201 |
202 | public void addHeader(String key, String value) {
203 | responseHeadersTable.addRow(key, value);
204 | }
205 |
206 | public void clearHeadersTable() {
207 | responseHeadersTable.removeAllRows();
208 | }
209 |
210 | public void setStatus(String status) {
211 | statusLabel.setText(status);
212 | revalidate();
213 | repaint();
214 | }
215 |
216 | public void clearStatus() {
217 | statusLabel.setText("");
218 | revalidate();
219 | repaint();
220 | }
221 |
222 | public void clear() {
223 | previewButton.setVisible(false);
224 | clearResponse();
225 | clearStatus();
226 | clearHeadersTable();
227 | }
228 |
229 | }
230 |
--------------------------------------------------------------------------------
/src/main/java/com/javierllorente/netbeans/rest/client/http/editor/sidebar/request/RequestProcessor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2025 Christian Lenz .
3 | *
4 | * Licensed to the Apache Software Foundation (ASF) under one
5 | * or more contributor license agreements. See the NOTICE file
6 | * distributed with this work for additional information
7 | * regarding copyright ownership. The ASF licenses this file
8 | * to you under the Apache License, Version 2.0 (the
9 | * "License"); you may not use this file except in compliance
10 | * with the License. You may obtain a copy of the License at
11 | *
12 | * http://www.apache.org/licenses/LICENSE-2.0
13 | *
14 | * Unless required by applicable law or agreed to in writing,
15 | * software distributed under the License is distributed on an
16 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 | * KIND, either express or implied. See the License for the
18 | * specific language governing permissions and limitations
19 | * under the License.
20 | */
21 | package com.javierllorente.netbeans.rest.client.http.editor.sidebar.request;
22 |
23 | import com.javierllorente.netbeans.rest.client.RestClient;
24 | import com.javierllorente.netbeans.rest.client.http.editor.sidebar.ResponseSidebarManager;
25 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.antlr.HTTPLexer;
26 | import com.javierllorente.netbeans.rest.client.http.editor.syntax.antlr.HTTPParser;
27 | import com.javierllorente.netbeans.rest.client.ui.RestClientTopComponent;
28 | import jakarta.ws.rs.ProcessingException;
29 | import jakarta.ws.rs.core.MultivaluedHashMap;
30 | import jakarta.ws.rs.core.MultivaluedMap;
31 | import java.util.ArrayList;
32 | import java.util.List;
33 | import javax.swing.text.JTextComponent;
34 | import javax.swing.text.StyledDocument;
35 | import org.antlr.v4.runtime.CharStreams;
36 | import org.antlr.v4.runtime.CommonTokenStream;
37 | import org.openide.text.NbDocument;
38 |
39 | /**
40 | *
41 | * @author Christian Lenz
42 | */
43 | public class RequestProcessor implements IRequestProcessor {
44 |
45 | public final List currentRequests;
46 | private final StyledDocument document;
47 | private final RestClient restClient;
48 | private JTextComponent textComponent;
49 | private RestClientTopComponent restClientUi;
50 |
51 | public RequestProcessor(StyledDocument document) {
52 | this.currentRequests = new ArrayList<>();
53 | this.document = document;
54 |
55 | this.restClient = new RestClient();
56 | }
57 |
58 | /**
59 | * Set the text component for this processor. This is used to show responses
60 | * in the sidebar.
61 | */
62 | public void setTextComponent(JTextComponent textComponent) {
63 | this.textComponent = textComponent;
64 | }
65 |
66 | @Override
67 | public List getRequests() {
68 | return currentRequests;
69 | }
70 |
71 | @Override
72 | public void updateRequestsList(String text) {
73 | currentRequests.clear();
74 |
75 | HTTPLexer lexer = new HTTPLexer(CharStreams.fromString(text));
76 | CommonTokenStream tokens = new CommonTokenStream(lexer);
77 | HTTPParser parser = new HTTPParser(tokens);
78 |
79 | HTTPParser.HttpRequestsFileContext fileCtx = parser.httpRequestsFile();
80 |
81 | if (fileCtx == null) {
82 | return;
83 | }
84 |
85 | List requestBlock = fileCtx.requestBlock();
86 |
87 | for (HTTPParser.RequestBlockContext requestBlockContext : requestBlock) {
88 | HTTPParser.RequestContext request = requestBlockContext.request();
89 |
90 | if (request == null) {
91 | return;
92 | }
93 |
94 | HTTPParser.RequestLineContext requestLine = request.requestLine();
95 |
96 | if (requestLine == null) {
97 | return;
98 | }
99 |
100 | currentRequests.add(new Request(request, requestLine.getText(), NbDocument.findLineNumber(document, requestLine.start.getStartIndex())));
101 | }
102 | }
103 |
104 | @Override
105 | public void callRequest(HTTPParser.RequestContext requestContext) {
106 | if (requestContext == null) {
107 | return;
108 | }
109 |
110 | HTTPParser.RequestLineContext requestLine = requestContext.requestLine();
111 |
112 | if (requestLine == null) {
113 | return;
114 | }
115 |
116 | HTTPParser.RequestTargetContext requestTarget = requestLine.requestTarget();
117 |
118 | if (requestTarget == null) {
119 | return;
120 | }
121 |
122 | this.restClient.setHeaders(getHeaders(requestContext.requestHeaders()));
123 | this.restClient.setBody(getBody(requestContext.requestBodySection()));
124 |
125 | try {
126 | String response = this.restClient.request(requestTarget.getText(), requestLine.METHOD() == null ? "GET" : requestLine.METHOD().getText());
127 |
128 | // Show response in sidebar if textComponent is available
129 | if (textComponent != null && response != null) {
130 | String contentType = "";
131 | var responseHeaders = this.restClient.getResponseHeaders();
132 |
133 | if (responseHeaders != null && responseHeaders.containsKey("content-type")
134 | && !responseHeaders.get("content-type").isEmpty()) {
135 | contentType = (String) responseHeaders.get("content-type").get(0);
136 | }
137 |
138 | // Pass headers to sidebar
139 | ResponseSidebarManager.getInstance().showResponse(textComponent, response, contentType, responseHeaders);
140 | }
141 | } catch (ProcessingException ex) {
142 | ResponseSidebarManager.getInstance().showResponse(textComponent, ex.getMessage(), "", null);
143 | }
144 | }
145 |
146 | @Override
147 | public void openRequestInUi(HTTPParser.RequestContext requestContext) {
148 | if (requestContext == null) {
149 | return;
150 | }
151 |
152 | HTTPParser.RequestLineContext requestLine = requestContext.requestLine();
153 |
154 | if (requestLine == null) {
155 | return;
156 | }
157 |
158 | HTTPParser.RequestTargetContext requestTarget = requestLine.requestTarget();
159 |
160 | if (requestTarget == null) {
161 | return;
162 | }
163 |
164 | String method = "GET"; // Default
165 | if (requestLine.METHOD() != null) {
166 | method = requestLine.METHOD().getText();
167 | }
168 |
169 | this.restClient.setMethod(method);
170 | this.restClient.setUri(requestTarget.getText());
171 | this.restClient.setHeaders(getHeaders(requestContext.requestHeaders()));
172 |
173 | String body = getBody(requestContext.requestBodySection());
174 |
175 | if (body != null) {
176 | restClient.setBodyType("Text");
177 | this.restClient.setBody(body);
178 | }
179 |
180 | restClientUi = new RestClientTopComponent(this.restClient);
181 | restClientUi.open();
182 | restClientUi.requestActive();
183 | }
184 |
185 | private MultivaluedMap getHeaders(HTTPParser.RequestHeadersContext requestHeadersContext) {
186 | MultivaluedMap headers = new MultivaluedHashMap<>();
187 |
188 | if (requestHeadersContext == null) {
189 | return headers;
190 | }
191 |
192 | List headerLine = requestHeadersContext.headerLine();
193 |
194 | for (HTTPParser.HeaderLineContext headerLineContext : headerLine) {
195 | HTTPParser.HeaderContext header = headerLineContext.header();
196 |
197 | if (header == null) {
198 | break;
199 | }
200 |
201 | HTTPParser.HeaderFieldContext headerField = header.headerField();
202 |
203 | if (headerField == null) {
204 | break;
205 | }
206 |
207 | HTTPParser.HeaderFieldNameContext headerFieldNameContext = headerField.headerFieldName();
208 | HTTPParser.HeaderFieldValueContext headerFieldValueContext = headerField.headerFieldValue();
209 |
210 | if (headerFieldNameContext == null || headerFieldValueContext == null) {
211 | break;
212 | }
213 |
214 | String key = headerFieldNameContext.getText();
215 | String value = headerFieldValueContext.getText();
216 |
217 | if (!headers.containsKey(key) || !headers.get(key).contains(value)) {
218 | headers.add(key, value);
219 | }
220 | }
221 |
222 | return headers;
223 | }
224 |
225 | private String getBody(HTTPParser.RequestBodySectionContext requestBodySectionContext) {
226 | if (requestBodySectionContext == null) {
227 | return "";
228 | }
229 |
230 | HTTPParser.RequestBodyContext requestBody = requestBodySectionContext.requestBody();
231 |
232 | if (requestBody == null) {
233 | return "";
234 | }
235 |
236 | if (requestBody.bodyWithStarter() != null) {
237 | HTTPParser.BodyContentContext bodyContent = requestBody.bodyWithStarter().bodyContent();
238 | return bodyContent != null ? bodyContent.getText() : "";
239 | } else if (requestBody.directBodyContent() != null) {
240 | return requestBody.directBodyContent().getText();
241 | }
242 |
243 | return "";
244 | }
245 | }
246 |
--------------------------------------------------------------------------------