| /******************************************************************************* |
| * Copyright (c) 2000, 2008 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.wst.jsdt.internal.ui.compare; |
| |
| import java.io.IOException; |
| |
| import org.eclipse.compare.CompareUI; |
| import org.eclipse.compare.IEditableContent; |
| import org.eclipse.compare.ISharedDocumentAdapter; |
| import org.eclipse.compare.IStreamContentAccessor; |
| import org.eclipse.compare.ITypedElement; |
| import org.eclipse.compare.structuremergeviewer.DocumentRangeNode; |
| import org.eclipse.compare.structuremergeviewer.IStructureComparator; |
| import org.eclipse.compare.structuremergeviewer.StructureCreator; |
| import org.eclipse.compare.structuremergeviewer.StructureRootNode; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IDocumentPartitioner; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.rules.FastPartitioner; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin; |
| import org.eclipse.wst.jsdt.internal.ui.propertiesfileeditor.IPropertiesFilePartitions; |
| import org.eclipse.wst.jsdt.internal.ui.propertiesfileeditor.PropertiesFilePartitionScanner; |
| |
| |
| public class PropertiesStructureCreator extends StructureCreator { |
| |
| /** |
| * A PropertyNode represents a key/value pair of a Java property file. |
| * The text range of a leg/value pair starts with an optional |
| * comment and ends right after the value. |
| */ |
| static class PropertyNode extends DocumentRangeNode implements ITypedElement, IAdaptable { |
| |
| public PropertyNode(DocumentRangeNode parent, int type, String id, String value, IDocument doc, int start, int length) { |
| super(parent, type, id, doc, start, length); |
| if (parent != null) { |
| parent.addChild(this); |
| } |
| } |
| |
| /* (non Java doc) |
| * see ITypedElement#getName |
| */ |
| public String getName() { |
| return getId(); |
| } |
| |
| /* (non Java doc) |
| * see ITypedElement#getType |
| */ |
| public String getType() { |
| return "properties2"; //$NON-NLS-1$ |
| } |
| |
| /* (non Java doc) |
| * see ITypedElement#getImage |
| */ |
| public Image getImage() { |
| return CompareUI.getImage(getType()); |
| } |
| } |
| |
| private static final String WHITESPACE= " \t\r\n\f"; //$NON-NLS-1$ |
| private static final String SEPARATORS= "=:"; //$NON-NLS-1$ |
| private static final String SEPARATORS2= SEPARATORS + WHITESPACE; |
| |
| |
| public PropertiesStructureCreator() { |
| } |
| |
| public String getName() { |
| return CompareMessages.PropertyCompareViewer_title; |
| } |
| |
| protected IStructureComparator createStructureComparator(Object input, |
| IDocument document, ISharedDocumentAdapter sharedDocumentAdapter, |
| IProgressMonitor monitor) throws CoreException { |
| |
| final boolean isEditable; |
| if (input instanceof IEditableContent) |
| isEditable= ((IEditableContent) input).isEditable(); |
| else |
| isEditable= false; |
| |
| DocumentRangeNode root= new StructureRootNode(document, input, this, sharedDocumentAdapter) { |
| public boolean isEditable() { |
| return isEditable; |
| } |
| }; |
| |
| try { |
| monitor = beginWork(monitor); |
| parsePropertyFile(root, document, monitor); |
| } catch (IOException ex) { |
| if (sharedDocumentAdapter != null) |
| sharedDocumentAdapter.disconnect(input); |
| throw new CoreException(new Status(IStatus.ERROR, JavaScriptPlugin.getPluginId(), 0, CompareMessages.PropertiesStructureCreator_error_occurred, ex)); |
| } finally { |
| monitor.done(); |
| } |
| |
| return root; |
| } |
| |
| public IStructureComparator locate(Object path, Object source) { |
| return null; |
| } |
| |
| public String getContents(Object node, boolean ignoreWhitespace) { |
| if (node instanceof IStreamContentAccessor) { |
| IStreamContentAccessor sca= (IStreamContentAccessor) node; |
| try { |
| return JavaCompareUtilities.readString(sca); |
| } catch (CoreException ex) { |
| JavaScriptPlugin.log(ex); |
| } |
| } |
| return null; |
| } |
| |
| private String readLine(int[] args, IDocument doc) { |
| int line= args[0]++; |
| try { |
| IRegion region= doc.getLineInformation(line); |
| int start= region.getOffset(); |
| int length= region.getLength(); |
| |
| try { |
| region= doc.getLineInformation(line+1); |
| args[1]= region.getOffset(); |
| } catch (BadLocationException ex) { |
| args[1]= doc.getLength(); |
| } |
| |
| return doc.get(start, length); |
| } catch (BadLocationException ex) { |
| // silently ignored |
| } |
| return null; |
| } |
| |
| private void parsePropertyFile(DocumentRangeNode root, IDocument doc, IProgressMonitor monitor) throws IOException { |
| |
| int start= -1; |
| int lineStart= 0; |
| |
| int[] args= new int[2]; |
| args[0]= 0; // here we return the line number |
| args[1]= 0; // and here the offset of the first character of the line |
| |
| for (;;) { |
| worked(monitor); |
| lineStart= args[1]; // start of current line |
| String line= readLine(args, doc); |
| if (line == null) |
| return; |
| |
| if (line.length() <= 0) |
| continue; // empty line |
| |
| char firstChar= line.charAt(0); |
| if (firstChar == '#' || firstChar == '!') { |
| if (start < 0) // comment belongs to next key/value pair |
| start= lineStart; |
| continue; // comment |
| } |
| |
| // find continuation lines |
| while (needNextLine(line)) { |
| String nextLine= readLine(args, doc); |
| if (nextLine == null) |
| nextLine= ""; //$NON-NLS-1$ |
| String line2= line.substring(0, line.length()-1); |
| int startPos= 0; |
| for (; startPos < nextLine.length(); startPos++) |
| if (WHITESPACE.indexOf(nextLine.charAt(startPos)) == -1) |
| break; |
| nextLine= nextLine.substring(startPos, nextLine.length()); |
| line= line2 + nextLine; |
| } |
| |
| // key start |
| int len= line.length(); |
| int keyPos= 0; |
| for (; keyPos < len; keyPos++) { |
| if (WHITESPACE.indexOf(line.charAt(keyPos)) == -1) |
| break; |
| } |
| |
| // key/value separator |
| int separatorPos; |
| for (separatorPos= keyPos; separatorPos < len; separatorPos++) { |
| char c= line.charAt(separatorPos); |
| if (c == '\\') |
| separatorPos++; |
| else if (SEPARATORS2.indexOf(c) != -1) |
| break; |
| } |
| |
| int valuePos; |
| for (valuePos= separatorPos; valuePos < len; valuePos++) |
| if (WHITESPACE.indexOf(line.charAt(valuePos)) == -1) |
| break; |
| |
| if (valuePos < len) |
| if (SEPARATORS.indexOf(line.charAt(valuePos)) != -1) |
| valuePos++; |
| |
| while (valuePos < len) { |
| if (WHITESPACE.indexOf(line.charAt(valuePos)) == -1) |
| break; |
| valuePos++; |
| } |
| |
| String key= convert(line.substring(keyPos, separatorPos)); |
| if (key.length() > 0) { |
| |
| if (start < 0) |
| start= lineStart; |
| |
| String value= ""; //$NON-NLS-1$ |
| if (separatorPos < len) |
| value= convert(line.substring(valuePos, len)); |
| |
| int length= args[1] - start; |
| |
| try { |
| String s= doc.get(start, length); |
| for (int i= s.length()-1; i >= 0; i--) { |
| char c= s.charAt(i); |
| if (c !='\r' && c != '\n') |
| break; |
| length--; |
| } |
| } catch (BadLocationException e) { |
| // silently ignored |
| } |
| |
| new PropertyNode(root, 0, key, value, doc, start, length); |
| start= -1; |
| } |
| } |
| } |
| |
| private boolean needNextLine(String line) { |
| int slashes= 0; |
| int ix= line.length() - 1; |
| while ((ix >= 0) && (line.charAt(ix--) == '\\')) |
| slashes++; |
| return slashes % 2 == 1; |
| } |
| |
| /* |
| * Converts escaped characters to Unicode. |
| */ |
| private String convert(String s) { |
| int l= s.length(); |
| StringBuffer buf= new StringBuffer(l); |
| int i= 0; |
| |
| while (i < l) { |
| char c= s.charAt(i++); |
| if (c == '\\') { |
| c= s.charAt(i++); |
| if (c == 'u') { |
| int v= 0; |
| for (int j= 0; j < 4; j++) { |
| c= s.charAt(i++); |
| switch (c) { |
| case '0': case '1': case '2': case '3': case '4': |
| case '5': case '6': case '7': case '8': case '9': |
| v= (v << 4) + (c-'0'); |
| break; |
| case 'a': case 'b': case 'c': |
| case 'd': case 'e': case 'f': |
| v= (v << 4) + 10+(c-'a'); |
| break; |
| case 'A': case 'B': case 'C': |
| case 'D': case 'E': case 'F': |
| v= (v << 4) + 10+(c - 'A'); |
| break; |
| default: |
| throw new IllegalArgumentException(CompareMessages.PropertyCompareViewer_malformedEncoding); |
| } |
| } |
| buf.append((char)v); |
| } else { |
| switch (c) { |
| case 't': |
| c= '\t'; |
| break; |
| case 'r': |
| c= '\r'; |
| break; |
| case 'n': |
| c= '\n'; |
| break; |
| case 'f': |
| c= '\f'; |
| break; |
| } |
| buf.append(c); |
| } |
| } else |
| buf.append(c); |
| } |
| return buf.toString(); |
| } |
| |
| protected IDocumentPartitioner getDocumentPartitioner() { |
| return new FastPartitioner(new PropertiesFilePartitionScanner(), IPropertiesFilePartitions.PARTITIONS); |
| } |
| |
| protected String getDocumentPartitioning() { |
| return IPropertiesFilePartitions.PROPERTIES_FILE_PARTITIONING; |
| } |
| |
| private void worked(IProgressMonitor monitor) { |
| if (monitor.isCanceled()) |
| throw new OperationCanceledException(); |
| monitor.worked(1); |
| } |
| |
| private IProgressMonitor beginWork(IProgressMonitor monitor) { |
| if (monitor == null) |
| return new NullProgressMonitor(); |
| return new SubProgressMonitor(monitor, IProgressMonitor.UNKNOWN); |
| } |
| |
| } |