View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2008 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
11   */
12  
13  package com.eviware.soapui.support.editor.views.xml.source;
14  
15  import com.eviware.soapui.SoapUI;
16  import com.eviware.soapui.impl.wsdl.panels.teststeps.support.LineNumbersPanel;
17  import com.eviware.soapui.settings.UISettings;
18  import com.eviware.soapui.support.DocumentListenerAdapter;
19  import com.eviware.soapui.support.UISupport;
20  import com.eviware.soapui.support.components.PreviewCorner;
21  import com.eviware.soapui.support.editor.views.AbstractXmlEditorView;
22  import com.eviware.soapui.support.editor.xml.XmlDocument;
23  import com.eviware.soapui.support.editor.xml.XmlEditor;
24  import com.eviware.soapui.support.editor.xml.XmlLocation;
25  import com.eviware.soapui.support.editor.xml.support.ValidationError;
26  import com.eviware.soapui.support.swing.SoapUISplitPaneUI;
27  import com.eviware.soapui.support.xml.JXEditTextArea;
28  import com.eviware.soapui.support.xml.actions.FormatXmlAction;
29  import com.eviware.soapui.support.xml.actions.LoadXmlTextAreaAction;
30  import com.eviware.soapui.support.xml.actions.SaveXmlTextAreaAction;
31  import org.apache.xmlbeans.XmlError;
32  import org.apache.xmlbeans.XmlException;
33  import org.apache.xmlbeans.XmlObject;
34  import org.apache.xmlbeans.XmlOptions;
35  
36  import javax.swing.*;
37  import javax.swing.text.Document;
38  import java.awt.*;
39  import java.awt.event.ActionEvent;
40  import java.awt.event.ActionListener;
41  import java.awt.event.MouseAdapter;
42  import java.awt.event.MouseEvent;
43  import java.beans.PropertyChangeListener;
44  import java.util.ArrayList;
45  import java.util.List;
46  
47  /***
48   * Default "XML" source editor view in soapUI
49   * 
50   * @author ole.matzura
51   */
52  
53  public class XmlSourceEditorView extends AbstractXmlEditorView<XmlDocument> implements PropertyChangeListener
54  {
55  	private JXEditTextArea editArea;
56  	private ValidateMessageXmlAction validateXmlAction;
57  	private JSplitPane splitter;
58  	private JScrollPane errorScrollPane;
59  	private DefaultListModel errorListModel;
60  	private FormatXmlAction formatXmlAction;
61  	private SaveXmlTextAreaAction saveXmlTextAreaAction;
62  	private boolean updating;
63  	private JPopupMenu editorPopup;
64  	public boolean isLocating;
65  	private JScrollPane editorScrollPane;
66  	private LoadXmlTextAreaAction loadXmlTextAreaAction;
67  	private JPopupMenu inputPopup;
68  	private LineNumbersPanel lineNumbersPanel;
69  	private JCheckBoxMenuItem toggleLineNumbersMenuItem;
70  	private PreviewCorner previewCorner;
71  
72  	public XmlSourceEditorView( XmlEditor<XmlDocument> xmlEditor )
73     {
74     	super( "XML", xmlEditor, XmlSourceEditorViewFactory.VIEW_ID );
75     }
76  	
77  	protected void buildUI()
78  	{
79  		editArea = JXEditTextArea.createXmlEditor( false );
80  		editArea.setMinimumSize(new Dimension(50, 50));
81  		editArea.setCaretPosition(0);
82  		editArea.setDiscardEditsOnSet(false);
83  		editArea.setEnabled( false );
84  		editArea.setBorder( BorderFactory.createMatteBorder( 0, 2, 0, 0, Color.WHITE) );
85  
86  		errorListModel = new DefaultListModel();
87  		JList list = new JList(errorListModel);
88  		list.addMouseListener(new ValidationListMouseAdapter(list, editArea));
89  		errorScrollPane = new JScrollPane(list);
90  		errorScrollPane.setVisible(false);
91  
92  		splitter = new JSplitPane(JSplitPane.VERTICAL_SPLIT)
93  		{
94  			public void requestFocus()
95  			{
96  				SwingUtilities.invokeLater( new Runnable() {
97  
98  					public void run()
99  					{
100 						editArea.requestFocusInWindow();
101 					}} );
102 			}
103 			
104 			public boolean hasFocus()
105 			{
106 				return editArea.hasFocus();
107 			}
108 		};
109 		
110 		splitter.setUI( new SoapUISplitPaneUI() );
111 		splitter.setDividerSize( 0 );
112 		splitter.setOneTouchExpandable( true );
113       
114 		lineNumbersPanel = new LineNumbersPanel( editArea );
115 		lineNumbersPanel.setVisible( SoapUI.getSettings().getBoolean( UISettings.SHOW_XML_LINE_NUMBERS ) ); 
116 		
117 		editorPopup = new JPopupMenu();
118 		buildPopup( editorPopup, editArea );	
119 		
120 		editArea.setRightClickPopup(editorPopup);
121 		editArea.getDocument().addDocumentListener(
122 				new DocumentListenerAdapter() {
123 
124 					public void update(Document document) 
125 					{
126 						if( !updating && getDocument() != null )
127 						{
128 							updating = true;
129 							getDocument().setXml( editArea.getText());
130 							updating = false;
131 						}
132 					}
133 				});
134 
135 		editArea.getInputHandler().addKeyBinding("A+V", validateXmlAction);
136 		editArea.getInputHandler().addKeyBinding("A+F", formatXmlAction);
137 		editArea.getInputHandler().addKeyBinding("C+S", saveXmlTextAreaAction);
138 		editArea.getInputHandler().addKeyBinding( "ALT+L", new ActionListener() {
139 
140 			public void actionPerformed( ActionEvent e )
141 			{
142 				lineNumbersPanel.setVisible( !lineNumbersPanel.isVisible() );
143 				toggleLineNumbersMenuItem.setSelected( lineNumbersPanel.isVisible() );
144 			}} );
145 		
146 		JPanel p = new JPanel( new BorderLayout() );
147 		p.add( editArea, BorderLayout.CENTER );
148 		p.add( lineNumbersPanel, BorderLayout.WEST );
149 		
150 		editorScrollPane = new JScrollPane(p);
151 		splitter.setTopComponent(editorScrollPane);
152 		splitter.setBottomComponent(errorScrollPane);
153 		splitter.setDividerLocation(1.0);
154 		splitter.setBorder(null);
155 		
156 		previewCorner = UISupport.addPreviewCorner( getEditorScrollPane(), true );
157 	}
158 
159 	public JScrollPane getEditorScrollPane()
160 	{
161 		return editorScrollPane;
162 	}
163 
164 	protected void buildPopup(JPopupMenu inputPopup, JXEditTextArea editArea )
165 	{
166 		this.inputPopup = inputPopup;
167 		validateXmlAction = new ValidateMessageXmlAction( );
168 		formatXmlAction = new FormatXmlAction(editArea);
169 		saveXmlTextAreaAction = new SaveXmlTextAreaAction( editArea, "Save" );
170 		loadXmlTextAreaAction = new LoadXmlTextAreaAction( editArea, "Load" );
171 		
172 		toggleLineNumbersMenuItem = new JCheckBoxMenuItem( "Show Line Numbers", lineNumbersPanel.isVisible());
173 		toggleLineNumbersMenuItem.setAccelerator( UISupport.getKeyStroke( "alt L" ) );
174 		toggleLineNumbersMenuItem.addActionListener( new ActionListener() {
175 
176 			public void actionPerformed( ActionEvent e )
177 			{
178 				lineNumbersPanel.setVisible( toggleLineNumbersMenuItem.isSelected() );
179 			}} );
180 		
181 		inputPopup.add(validateXmlAction);
182 		inputPopup.add(formatXmlAction);
183 		inputPopup.addSeparator();
184 		inputPopup.add(editArea.getUndoAction());
185 		inputPopup.add(editArea.getRedoAction());
186 		inputPopup.add(editArea.createCopyAction());
187 		inputPopup.add(editArea.createCutAction());
188 		inputPopup.add(editArea.createPasteAction());
189 		inputPopup.addSeparator();
190 		inputPopup.add(editArea.getFindAndReplaceAction());
191 		inputPopup.addSeparator();
192 		inputPopup.add(editArea.getGoToLineAction());
193 		inputPopup.add( toggleLineNumbersMenuItem );
194 		
195 		inputPopup.addSeparator(); 
196 		inputPopup.add(saveXmlTextAreaAction);
197 		inputPopup.add(loadXmlTextAreaAction);
198 	}
199 	
200 	@Override
201 	public void release()
202 	{
203 		super.release();
204 		inputPopup.removeAll();
205 		previewCorner.release();
206 	}
207 
208 	private final static class ValidationListMouseAdapter extends MouseAdapter 
209 	{
210 		private final JList list;
211 
212 		private final JXEditTextArea textArea;
213 
214 		public ValidationListMouseAdapter(JList list, JXEditTextArea textArea) 
215 		{
216 			this.list = list;
217 			this.textArea = textArea;
218 		}
219 
220 		public void mouseClicked(MouseEvent e) 
221 		{
222 			if (e.getClickCount() < 2)
223 				return;
224 
225 			int ix = list.getSelectedIndex();
226 			if (ix == -1)
227 				return;
228 
229 			Object obj = list.getModel().getElementAt(ix);
230 			if (obj instanceof ValidationError) 
231 			{
232 				ValidationError error = (ValidationError) obj;
233 				if (error.getLineNumber() >= 0) 
234 				{
235 					textArea.setCaretPosition(textArea.getLineStartOffset(error
236 							.getLineNumber() - 1));
237 					textArea.requestFocus();
238 				} 
239 				else
240 					Toolkit.getDefaultToolkit().beep();
241 			} 
242 			else
243 				Toolkit.getDefaultToolkit().beep();
244 		}
245 	}
246 
247 	public JXEditTextArea getInputArea()
248 	{
249 		getComponent();
250 		return editArea;
251 	}
252 
253 	public void setEditable(boolean enabled)
254 	{
255 		getComponent();
256 	   editArea.setEditable( enabled );	
257 	}
258 
259 	protected ValidationError[] validateXml( String xml )
260 	{
261       try
262       {
263          XmlObject.Factory.parse( xml, new XmlOptions().setLoadLineNumbers() );
264       }
265       catch( XmlException e )
266       {
267          List<ValidationError> result = new ArrayList<ValidationError>();
268 
269          if( e.getErrors() != null )
270          {
271             for( Object error : e.getErrors())
272             {
273                if( error instanceof XmlError )
274                   result.add( new com.eviware.soapui.model.testsuite.AssertionError( (XmlError) error ));
275                else
276                   result.add( new com.eviware.soapui.model.testsuite.AssertionError( error.toString() ));
277             }
278          }
279 
280          if( result.isEmpty())
281            result.add( new com.eviware.soapui.model.testsuite.AssertionError( e.toString() ));
282 
283          return result.toArray( new ValidationError[result.size()] );
284       }
285 
286       return null;
287 	}
288 	
289 	public class ValidateMessageXmlAction extends AbstractAction
290 	{
291 		public ValidateMessageXmlAction()
292 		{
293 			super( "Validate" );
294 			putValue( Action.ACCELERATOR_KEY, UISupport.getKeyStroke( "alt V" ));
295 		}
296 
297 		public void actionPerformed(ActionEvent e)
298 		{
299 			if( validate())
300 				UISupport.showInfoMessage( "Validation OK" ); 
301 		}
302 	}
303 
304    public boolean activate(XmlLocation location)
305 	{
306    	super.activate( location );
307    	
308    	if( location != null )
309    		setLocation( location );
310    	
311    	editArea.requestFocus();
312    	
313    	return true;
314 	}
315 
316 	public JComponent getComponent()
317 	{
318 		if( splitter == null )
319 			buildUI();
320 		
321 		return splitter;
322 	}
323 	
324 	public XmlLocation getEditorLocation()
325 	{
326 		return new XmlLocation( getCurrentLine()+1, getCurrentColumn() );
327 	}
328 	
329 	public void setLocation(XmlLocation location)
330 	{
331 		int line = location.getLine()-1;
332 		if( location != null && line >= 0 )
333 		{
334 			int caretLine = editArea.getCaretLine();
335 			int offset = editArea.getLineStartOffset( line );
336 			
337 			try
338 			{
339 				editArea.setCaretPosition( offset + location.getColumn() );
340 				int scrollLine = line + (line > caretLine ? 3 : -3);
341 				if( scrollLine >= editArea.getLineCount() )
342 					scrollLine = editArea.getLineCount()-1;
343 				else if( scrollLine < 0 )
344 					scrollLine = 0;
345 				
346 				editArea.scrollTo( scrollLine, location.getColumn() );
347 			}
348 			catch( RuntimeException e )
349 			{
350 			}
351 		}
352 	}
353 
354 	public int getCurrentLine()
355 	{
356 		if( editArea == null )
357 			return -1;
358 		return editArea.getCaretLine();
359 	}
360 	
361 	public int getCurrentColumn()
362 	{
363 		if( editArea == null )
364 			return -1;
365 		return editArea.getCaretColumn();
366 	}
367 
368 	public String getText()
369 	{
370 		if( editArea == null )
371 			return null;
372 		return editArea.getText();
373 	}
374 
375 	public boolean validate()
376 	{
377 		ValidationError[] errors = validateXml( editArea.getText() );
378 		
379 		errorListModel.clear();
380 		if( errors == null || errors.length == 0 )
381 		{
382 			splitter.setDividerLocation( 1.0 );
383 			splitter.setDividerSize( 0 );
384 		   errorScrollPane.setVisible( false );
385 		   return true;
386 		}
387 		else
388 		{
389 			Toolkit.getDefaultToolkit().beep();
390 		   for( int c = 0; c < errors.length; c++ )
391 		   {
392 		   	errorListModel.addElement( errors[c] );
393 		   }
394 		   errorScrollPane.setVisible( true );
395 		   splitter.setDividerLocation( 0.8 );
396 		   splitter.setDividerSize( 10 );
397 		   return false;
398 		}
399 	}
400 
401 	public void setXml(String xml)
402 	{
403 		if( !updating )
404 		{
405 			updating = true;
406 			
407 			if( xml == null )
408 			{
409 				editArea.setText( "" );
410 				editArea.setEnabled( false );
411 			}
412 			else
413 			{
414 				int caretPosition = editArea.getCaretPosition();
415 				
416 				editArea.setEnabled( true );
417 				editArea.setText( xml );
418 				
419 				editArea.setCaretPosition( caretPosition < xml.length() ? caretPosition : 0 );
420 			}
421 			
422 			updating = false;
423 		}
424 	}
425 	
426 	public boolean saveDocument( boolean validate )
427 	{
428 		return validate ? validate() : true;
429 	}
430 	
431 	public void locationChanged(XmlLocation location)
432 	{
433 		isLocating = true;
434 		setLocation( location );
435 		isLocating = false;
436 	}
437 
438 	public JPopupMenu getEditorPopup()
439 	{
440 		return editorPopup;
441 	}
442 
443 	public boolean hasFocus()
444 	{
445 		return editArea.hasFocus();
446 	}
447 	
448 	public boolean isInspectable()
449 	{
450 		return true;
451 	}
452 
453 	public ValidateMessageXmlAction getValidateXmlAction()
454 	{
455 		return validateXmlAction;
456 	}
457 }