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