View Javadoc

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