View Javadoc

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