alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*******************************************************************************
 * Copyright (c) 2003, 2007 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.pde.internal.ui.editor.context;

import java.util.*;
import org.eclipse.core.filebuffers.IDocumentSetupParticipant;
import org.eclipse.jface.text.*;
import org.eclipse.pde.core.IModelChangedEvent;
import org.eclipse.pde.internal.core.text.*;
import org.eclipse.pde.internal.core.util.PDEXMLHelper;
import org.eclipse.pde.internal.ui.editor.PDEFormEditor;
import org.eclipse.text.edits.*;
import org.eclipse.ui.IEditorInput;

public abstract class XMLInputContext extends UTF8InputContext {
	protected HashMap fOperationTable = new HashMap();
	protected HashMap fMoveOperations = new HashMap();

	/**
	 * @param editor
	 * @param input
	 */
	public XMLInputContext(PDEFormEditor editor, IEditorInput input, boolean primary) {
		super(editor, input, primary);
	}

	protected IDocumentSetupParticipant getDocumentSetupParticipant() {
		return new XMLDocumentSetupParticpant();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.pde.internal.ui.neweditor.context.InputContext#addTextEditOperation(java.util.ArrayList, org.eclipse.pde.core.IModelChangedEvent)
	 */
	protected void addTextEditOperation(ArrayList ops, IModelChangedEvent event) {
		Object[] objects = event.getChangedObjects();
		if (objects != null) {
			for (int i = 0; i < objects.length; i++) {
				Object object = objects[i];
				switch (event.getChangeType()) {
					case IModelChangedEvent.REMOVE :
						if (object instanceof IDocumentElementNode)
							removeNode((IDocumentElementNode) object, ops);
						break;
					case IModelChangedEvent.INSERT :
						if (object instanceof IDocumentElementNode)
							insertNode((IDocumentElementNode) object, ops);
						break;
					case IModelChangedEvent.CHANGE :
						if (object instanceof IDocumentElementNode) {
							IDocumentElementNode node = (IDocumentElementNode) object;
							IDocumentAttributeNode attr = node.getDocumentAttribute(event.getChangedProperty());
							if (attr != null) {
								addAttributeOperation(attr, ops, event);
							} else if (event.getOldValue() instanceof IDocumentElementNode && event.getNewValue() instanceof IDocumentElementNode) {
								// swapping of nodes
								modifyNode(node, ops, event);
							}
						} else if (object instanceof IDocumentTextNode) {
							addElementContentOperation((IDocumentTextNode) object, ops);
						}
					default :
						break;
				}
			}
		}
	}

	private void removeNode(IDocumentElementNode node, ArrayList ops) {
		// delete previous op on this node, if any
		TextEdit old = (TextEdit) fOperationTable.get(node);
		if (old != null) {
			ops.remove(old);
			fOperationTable.remove(node);
		}
		TextEdit oldMove = (TextEdit) fMoveOperations.get(node);
		if (oldMove != null) {
			ops.remove(oldMove);
			fMoveOperations.remove(node);
		}
		// if node has an offset, delete it
		if (node.getOffset() > -1) {
			// Create a delete op for this node
			TextEdit op = getDeleteNodeOperation(node);
			ops.add(op);
			fOperationTable.put(node, op);
		} else if (old == null && oldMove == null) {
			// No previous op on this non-offset node, just rewrite highest ancestor with an offset
			insertNode(node, ops);
		}
	}

	private void insertNode(IDocumentElementNode node, ArrayList ops) {
		TextEdit op = null;
		node = getHighestNodeToBeWritten(node);
		if (node.getParentNode() == null) {
			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=163161
			if (node.isRoot())
				op = new InsertEdit(0, node.write(true));
		} else {
			if (node.getOffset() > -1) {
				// this is an element that was of the form <element/>
				// it now needs to be broken up into <element>
				op = new ReplaceEdit(node.getOffset(), node.getLength(), node.write(false));
			} else {
				// try to insert after last sibling that has an offset
				op = insertAfterSibling(node);
				// insert as first child of its parent
				if (op == null) {
					op = insertAsFirstChild(node);
				}
			}
		}
		TextEdit old = (TextEdit) fOperationTable.get(node);
		if (old != null)
			ops.remove(old);
		if (op != null) {
			ops.add(op);
			fOperationTable.put(node, op);
		}
	}

	private InsertEdit insertAfterSibling(IDocumentElementNode node) {
		IDocumentElementNode sibling = node.getPreviousSibling();
		for (;;) {
			if (sibling == null)
				break;
			if (sibling.getOffset() > -1) {
				node.setLineIndent(sibling.getLineIndent());
				String sep = TextUtilities.getDefaultLineDelimiter(getDocumentProvider().getDocument(getInput()));
				return new InsertEdit(sibling.getOffset() + sibling.getLength(), sep + node.write(true));
			}
			sibling = sibling.getPreviousSibling();
		}
		return null;
	}

	private InsertEdit insertAsFirstChild(IDocumentElementNode node) {
		int offset = node.getParentNode().getOffset();
		int length = getNextPosition(getDocumentProvider().getDocument(getInput()), offset, '>');
		node.setLineIndent(node.getParentNode().getLineIndent() + 3);
		String sep = TextUtilities.getDefaultLineDelimiter(getDocumentProvider().getDocument(getInput()));
		return new InsertEdit(offset + length + 1, sep + node.write(true));
	}

	private void modifyNode(IDocumentElementNode node, ArrayList ops, IModelChangedEvent event) {
		IDocumentElementNode oldNode = (IDocumentElementNode) event.getOldValue();
		IDocumentElementNode newNode = (IDocumentElementNode) event.getNewValue();

		IDocumentElementNode node1 = (oldNode.getPreviousSibling() == null || oldNode.equals(newNode.getPreviousSibling())) ? oldNode : newNode;
		IDocumentElementNode node2 = node1.equals(oldNode) ? newNode : oldNode;

		if (node1.getOffset() < 0 && node2.getOffset() < 2) {
			TextEdit op = (TextEdit) fOperationTable.get(node1);
			if (op == null) {
				// node 1 has no rule, so node 2 has no rule, therefore rewrite parent/ancestor
				insertNode(node, ops);
			} else {
				// swap order of insert operations
				TextEdit op2 = (TextEdit) fOperationTable.get(node2);
				ops.set(ops.indexOf(op), op2);
				ops.set(ops.indexOf(op2), op);
			}
		} else if (node1.getOffset() > -1 && node2.getOffset() > -1) {
			// both nodes have offsets, so create a move target/source combo operation
			IRegion region = getMoveRegion(node1);
			MoveSourceEdit source = new MoveSourceEdit(region.getOffset(), region.getLength());
			region = getMoveRegion(node2);
			source.setTargetEdit(new MoveTargetEdit(region.getOffset()));
			MoveSourceEdit op = (MoveSourceEdit) fMoveOperations.get(node1);
			if (op != null) {
				ops.set(ops.indexOf(op), source);
			} else {
				op = (MoveSourceEdit) fMoveOperations.get(node2);
				if (op != null && op.getTargetEdit().getOffset() == source.getOffset()) {
					fMoveOperations.remove(node2);
					ops.remove(op);
					return;
				}
				ops.add(source);
			}
			fMoveOperations.put(node1, source);
		} else {
			// one node with offset, the other without offset.  Delete/reinsert the one without offset
			insertNode((node1.getOffset() < 0) ? node1 : node2, ops);
		}
	}

	private IRegion getMoveRegion(IDocumentElementNode node) {
		int offset = node.getOffset();
		int length = node.getLength();
		int i = 1;
		try {
			IDocument doc = getDocumentProvider().getDocument(getInput());
			for (;; i++) {
				char ch = doc.get(offset - i, 1).toCharArray()[0];
				if (!Character.isWhitespace(ch)) {
					i -= 1;
					break;
				}
			}
		} catch (BadLocationException e) {
		}
		return new Region(offset - i, length + i);
	}

	private void addAttributeOperation(IDocumentAttributeNode attr, ArrayList ops, IModelChangedEvent event) {
		int offset = attr.getValueOffset();
		Object newValue = event.getNewValue();
		Object changedObject = attr;
		TextEdit op = null;
		if (offset > -1) {
			if (newValue == null || newValue.toString().length() == 0) {
				int length = attr.getValueOffset() + attr.getValueLength() + 1 - attr.getNameOffset();
				op = getAttributeDeleteEditOperation(attr.getNameOffset(), length);
			} else {
				op = new ReplaceEdit(offset, attr.getValueLength(), getWritableAttributeNodeValue(event.getNewValue().toString()));
			}
		}

		if (op == null) {
			IDocumentElementNode node = attr.getEnclosingElement();
			IDocument doc = getDocumentProvider().getDocument(getInput());
			if (node.getOffset() > -1) {
				changedObject = node;
				int len = getNextPosition(doc, node.getOffset(), '>');
				op = new ReplaceEdit(node.getOffset(), len + 1, node.writeShallow(shouldTerminateElement(doc, node.getOffset() + len)));
			} else {
				insertNode(node, ops);
				return;
			}
		}
		TextEdit oldOp = (TextEdit) fOperationTable.get(changedObject);
		if (oldOp != null)
			ops.remove(oldOp);
		ops.add(op);
		fOperationTable.put(changedObject, op);
	}

	private void addElementContentOperation(IDocumentTextNode textNode, ArrayList ops) {
		TextEdit op = null;
		Object changedObject = textNode;
		if (textNode.getOffset() > -1) {
			String newText = getWritableTextNodeString(textNode);
			op = new ReplaceEdit(textNode.getOffset(), textNode.getLength(), newText);
		} else {
			IDocumentElementNode parent = textNode.getEnclosingElement();
			if (parent.getOffset() > -1) {
				IDocument doc = getDocumentProvider().getDocument(getInput());
				try {
					String endChars = doc.get(parent.getOffset() + parent.getLength() - 2, 2);
					if ("/>".equals(endChars)) { //$NON-NLS-1$
						// parent element is of the form <element/>, rewrite it
						insertNode(parent, ops);
						return;
					}
				} catch (BadLocationException e) {
				}
				// add text as first child
				changedObject = parent;
				String sep = TextUtilities.getDefaultLineDelimiter(getDocumentProvider().getDocument(getInput()));
				StringBuffer buffer = new StringBuffer(sep);
				for (int i = 0; i < parent.getLineIndent(); i++)
					buffer.append(" "); //$NON-NLS-1$
				buffer.append("   " + getWritableTextNodeString(textNode)); //$NON-NLS-1$
				int offset = parent.getOffset();
				int length = getNextPosition(doc, offset, '>');
				op = new InsertEdit(offset + length + 1, buffer.toString());
			} else {
				insertNode(parent, ops);
				return;
			}
		}
		TextEdit oldOp = (TextEdit) fOperationTable.get(changedObject);
		if (oldOp != null)
			ops.remove(oldOp);
		ops.add(op);
		fOperationTable.put(changedObject, op);
	}

	private boolean shouldTerminateElement(IDocument doc, int offset) {
		try {
			return doc.get(offset - 1, 1).toCharArray()[0] == '/';
		} catch (BadLocationException e) {
		}
		return false;
	}

	private int getNextPosition(IDocument doc, int offset, char ch) {
		int i = 0;
		try {
			for (i = 0; i + offset < doc.getLength(); i++) {
				if (ch == doc.getChar(offset + i))
					break;
			}
		} catch (BadLocationException e) {
		}
		return i;
	}

	private DeleteEdit getAttributeDeleteEditOperation(int offset, int length) {
		try {
			IDocument doc = getDocumentProvider().getDocument(getInput());
			// Traverse backwards in the document starting at the attribute
			// offset
			// Goal: Delete all whitespace preceding the attribute name 
			// including spaces, tabs and newlines 
			// We want the next attribute (if defined) to be in the same 
			// position as the deleted one and properly indented.  Otherwise,
			// we want the open angle bracket to be adjacent to the start 
			// element tag name or adjacent to the previous attribute (if
			// defined) before the deleted one
			// e.g.   _____\n________att1="value1"
			// This is accomplished by growing the length and decrementing
			// the offset in order to include the extra whitespace in the
			// deletion operation
			for (int i = (offset - 1); i >= 0; i--) {
				// Get the character at the specified document index
				char character = doc.getChar(i);
				// If the character is whitespace, include it in the deletion
				// operation
				if (Character.isWhitespace(character)) {
					// Grow length by one
					length = length + 1;
					// Decrement offset by one
					offset = offset - 1;
				} else {
					// Non-whitespace character encountered, do not mark it 
					// for deletion and we are done
					break;
				}
			}
		} catch (BadLocationException e) {
		}
		return new DeleteEdit(offset, length);
	}

	private DeleteEdit getDeleteNodeOperation(IDocumentElementNode node) {
		int offset = node.getOffset();
		int length = node.getLength();
		try {
			IDocument doc = getDocumentProvider().getDocument(getInput());
			// node starts on this line:
			int startLine = doc.getLineOfOffset(offset);
			// 1st char on startLine has this offset:
			int startLineOffset = doc.getLineOffset(startLine);
			// hunt down 1st whitespace/start of line with startOffset:
			int startOffset;
			// loop backwards to the beginning of the line, stop if we find non-whitespace
			for (startOffset = offset - 1; startOffset >= startLineOffset; startOffset -= 1)
				if (!Character.isWhitespace(doc.getChar(startOffset)))
					break;

			// move forward one (loop stopped after reaching too far)
			startOffset += 1;

			// node ends on this line:
			int endLine = doc.getLineOfOffset(offset + length);
			// length of last line's delimiter:
			int endLineDelimLength = doc.getLineDelimiter(endLine).length();
			// hunt last whitespace/end of line with extraLength:
			int extraLength = length;
			while (true) {
				extraLength += 1;
				if (!Character.isWhitespace(doc.getChar(offset + extraLength))) {
					// found non-white space, move back one
					extraLength -= 1;
					break;
				}
				if (doc.getLineOfOffset(offset + extraLength) > endLine) {
					// don't want to touch the lineDelimeters
					extraLength -= endLineDelimLength;
					break;
				}
			}

			// if we reached start of line, remove newline
			if (startOffset == startLineOffset)
				startOffset -= doc.getLineDelimiter(startLine).length();

			// add difference of new offset
			length = extraLength + (offset - startOffset);
			offset = startOffset;
//			printDeletionRange(offset, length);
		} catch (BadLocationException e) {
		}
		return new DeleteEdit(offset, length);
	}

	protected void printDeletionRange(int offset, int length) {
		try {
			// newlines printed as \n
			// carriage returns printed as \r
			// tabs printed as \t
			// spaces printed as *
			String string = getDocumentProvider().getDocument(getInput()).get(offset, length);
			StringBuffer buffer = new StringBuffer();
			for (int i = 0; i < string.length(); i++) {
				char c = string.charAt(i);
				if (c == '\n')
					buffer.append("\\n"); //$NON-NLS-1$
				else if (c == '\r')
					buffer.append("\\r"); //$NON-NLS-1$
				else if (c == '\t')
					buffer.append("\\t"); //$NON-NLS-1$
				else if (c == ' ')
					buffer.append('*');
				else
					buffer.append(c);
			}
			System.out.println(buffer.toString());
		} catch (BadLocationException e) {
		}
	}

	private IDocumentElementNode getHighestNodeToBeWritten(IDocumentElementNode node) {
		IDocumentElementNode parent = node.getParentNode();
		if (parent == null)
			return node;
		if (parent.getOffset() > -1) {
			IDocument doc = getDocumentProvider().getDocument(getInput());
			try {
				String endChars = doc.get(parent.getOffset() + parent.getLength() - 2, 2);
				return ("/>".equals(endChars)) ? parent : node; //$NON-NLS-1$
			} catch (BadLocationException e) {
				return node;
			}

		}
		return getHighestNodeToBeWritten(parent);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.pde.internal.ui.neweditor.context.InputContext#flushModel(org.eclipse.jface.text.IDocument)
	 */
	protected void flushModel(IDocument doc) {
		removeUnnecessaryOperations();
		if (fOperationTable.size() == 1) {
			Object object = fOperationTable.keySet().iterator().next();
			if (object instanceof IDocumentElementNode && fEditOperations.get(0) instanceof InsertEdit) {
				if (((IDocumentElementNode) object).getParentNode() == null) {
					doc.set(((IDocumentElementNode) object).write(true));
					fOperationTable.clear();
					fEditOperations.clear();
					return;
				}
			}
		}
		reorderInsertEdits(fEditOperations);
		fOperationTable.clear();
		fMoveOperations.clear();
		super.flushModel(doc);
	}

	protected abstract void reorderInsertEdits(ArrayList ops);

	protected void removeUnnecessaryOperations() {
		Iterator iter = fOperationTable.values().iterator();
		while (iter.hasNext()) {
			Object object = iter.next();
			if (object instanceof IDocumentElementNode) {
				IDocumentElementNode node = (IDocumentElementNode) object;
				if (node.getOffset() > -1) {
					IDocumentAttributeNode[] attrs = node.getNodeAttributes();
					for (int i = 0; i < attrs.length; i++) {
						Object op = fOperationTable.remove(attrs[i]);
						if (op != null)
							fEditOperations.remove(op);
					}
					IDocumentTextNode textNode = node.getTextNode();
					if (textNode != null) {
						Object op = fOperationTable.remove(textNode);
						if (op != null)
							fEditOperations.remove(op);
					}
				}
			}
		}
	}

	/**
	 * @param source
	 * @return
	 */
	protected String getWritableAttributeNodeValue(String source) {
		// TODO: MP: TEO: LOW: Shouldn't it be getWritableAttributeString ?
		return PDEXMLHelper.getWritableString(source);
	}

	/**
	 * @param source
	 * @return
	 */
	protected String getWritableTextNodeString(IDocumentTextNode textNode) {
		return textNode.write();
	}

	protected HashMap getOperationTable() {
		return fOperationTable;
	}

}
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.