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