content: /*******************************************************************************
   * Copyright (c) 2000, 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.wst.jsdt.internal.corext.refactoring.nls;
  
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  
  import org.eclipse.jface.text.BadLocationException;
  import org.eclipse.jface.text.IDocument;
  import org.eclipse.jface.text.TextUtilities;
  import org.eclipse.text.edits.DeleteEdit;
  import org.eclipse.text.edits.InsertEdit;
  import org.eclipse.text.edits.ReplaceEdit;
  
  public class PropertyFileDocumentModel {
  
  	private static final char[] HEX_DIGITS = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
      private List fKeyValuePairs;
      private String fLineDelimiter;
  
      public PropertyFileDocumentModel(IDocument document) {
          parsePropertyDocument(document);
          fLineDelimiter= TextUtilities.getDefaultLineDelimiter(document);
      }
      
      public int getIndex(String key) {
      	for (int i= 0; i < fKeyValuePairs.size(); i++) {
              KeyValuePairModell keyValuePair = (KeyValuePairModell) fKeyValuePairs.get(i);
              if (keyValuePair.getKey().equals(key)) {
              	return i;
              }
      	}
      	return -1;
      }
      
  
      public InsertEdit insert(String key, String value) {
          return insert(new KeyValuePair(key, value));
      }
  
      public InsertEdit insert(KeyValuePair keyValuePair) {
          KeyValuePairModell keyValuePairModell = new KeyValuePairModell(keyValuePair); 
          int index = findInsertPosition(keyValuePairModell);
          KeyValuePairModell insertHere = (KeyValuePairModell) fKeyValuePairs.get(index);
          int offset = insertHere.fOffset - insertHere.fLeadingWhiteSpaces;
          
          String extra= ""; //$NON-NLS-1$
          if (insertHere instanceof LastKeyValuePair && ((LastKeyValuePair)insertHere).needsNewLine()) {
          	extra= fLineDelimiter;
          	((LastKeyValuePair)insertHere).resetNeedsNewLine();
          }
          return new InsertEdit(offset, extra + keyValuePairModell.getEncodedText(fLineDelimiter));
      }
  
      public InsertEdit[] insert(KeyValuePair[] keyValuePairs) {
          InsertEdit[] inserts = new InsertEdit[keyValuePairs.length];
          for (int i = 0; i < keyValuePairs.length; i++) {            
              inserts[i] = insert(keyValuePairs[i]);
          }
          return inserts;        
      }
      
      public DeleteEdit remove(String key) {
      	for (Iterator iter = fKeyValuePairs.iterator(); iter.hasNext();) {
              KeyValuePairModell keyValuePair = (KeyValuePairModell) iter.next();
              if (keyValuePair.fKey.equals(key)) {
              	KeyValuePairModell next = (KeyValuePairModell) iter.next();
              	return new DeleteEdit(keyValuePair.fOffset, next.fOffset - keyValuePair.fOffset);
              }            
          }
          return null;
      }
      
      public ReplaceEdit replace(KeyValuePair toReplace, KeyValuePair replaceWith) {     
          for (Iterator iter = fKeyValuePairs.iterator(); iter.hasNext();) {
              KeyValuePairModell keyValuePair = (KeyValuePairModell) iter.next();
              if (keyValuePair.fKey.equals(toReplace.getKey())) {
                  String newText = new KeyValuePairModell(replaceWith).getEncodedText(fLineDelimiter);
                  KeyValuePairModell next = (KeyValuePairModell) iter.next();
                  int range = next.fOffset - keyValuePair.fOffset;
              	return new ReplaceEdit(keyValuePair.fOffset, range, newText);
              }            
          }
          return null;
      }
      
      private int findInsertPosition(KeyValuePairModell keyValuePair) {
          int insertIndex = 0;
          int maxMatch = Integer.MIN_VALUE;
          for (int i=0; i<fKeyValuePairs.size(); i++) {
              KeyValuePairModell element = (KeyValuePairModell) fKeyValuePairs.get(i);
              int match = element.compareTo(keyValuePair);
              if (match >= maxMatch) {
                  insertIndex = i;
                  maxMatch = match;
              }            
          }
          
          if (insertIndex < fKeyValuePairs.size() - 1) {
              insertIndex++;
          }
          
          return insertIndex;
      }    
  
      private void parsePropertyDocument(IDocument document) {
          fKeyValuePairs = new ArrayList();
        
          SimpleLineReader reader = new SimpleLineReader(document);        
          int offset = 0;
          String line = reader.readLine();
          int leadingWhiteSpaces = 0;
          while (line != null) {
              if (!SimpleLineReader.isCommentOrWhiteSpace(line)) {
                  int idx = getIndexOfSeparationCharacter(line);
                  if (idx != -1) {
                  	String key= line.substring(0, idx);
                  	String value= line.substring(idx + 1);
                      fKeyValuePairs.add(new KeyValuePairModell(key, value, offset, leadingWhiteSpaces));
                      leadingWhiteSpaces = 0;
                  }
              } else {
                  leadingWhiteSpaces += line.length();
              }
              offset += line.length();
              line = reader.readLine();
          }
          int lastLine= document.getNumberOfLines() - 1;
          boolean needsNewLine= false;
          try {
          	needsNewLine= !(document.getLineLength(lastLine) == 0);
  		} catch (BadLocationException ignore) {
  			// treat last line having no new line
  		}
          LastKeyValuePair lastKeyValuePair = new LastKeyValuePair(offset, needsNewLine);
  		fKeyValuePairs.add(lastKeyValuePair);
      } 
      
      private int getIndexOfSeparationCharacter(String line) {
          int minIndex = -1;
          int indexOfEven = line.indexOf('=');
          int indexOfColumn = line.indexOf(':');
          int indexOfBlank = line.indexOf(' ');
          
          if ((indexOfEven != -1) && (indexOfColumn != -1)) {
              minIndex = Math.min(indexOfEven, indexOfColumn);            
          } else {
              minIndex = Math.max(indexOfEven, indexOfColumn);
          }
          
          if ((minIndex != -1) && (indexOfBlank != -1)) {
              minIndex = Math.min(minIndex, indexOfBlank);            
          } else {
              minIndex = Math.max(minIndex, indexOfBlank);
          }
          
          return minIndex;        
      }  
      
      public static String unwindEscapeChars(String s){
  		StringBuffer sb= new StringBuffer(s.length());
  		int length= s.length();
  		for (int i= 0; i < length; i++){
  			char c= s.charAt(i);
  			sb.append(getUnwoundString(c));
  		}
  		return sb.toString();
  	}
  
  	public static String unwindValue(String value) {
  		return escapeLeadingWhiteSpaces(escapeCommentChars(unwindEscapeChars(value)));
  	}
  
  	private static String getUnwoundString(char c){
  	        	switch(c){
  	        		case '\b' :
  	        			return "\\b";//$NON-NLS-1$
  	        		case '\t' :
  	        			return "\\t";//$NON-NLS-1$
  	        		case '\n' :
  	        			return "\\n";//$NON-NLS-1$
  	        		case '\f' :
  	        			return "\\f";//$NON-NLS-1$	
  	        		case '\r' :
  	        			return "\\r";//$NON-NLS-1$
  
  //      			These can be used unescaped in properties file:
  //      			case '\"' :
  //      			return "\\\"";//$NON-NLS-1$
  //      			case '\'' :
  //      			return "\\\'";//$NON-NLS-1$
  	        			
  	        		case '\\' :
  	        			return "\\\\";//$NON-NLS-1$
  	        		
  //      			This is only done when writing to the .properties file in #unwindValue(String)
  //      			case '!':
  //      			return "\\!";//$NON-NLS-1$
  //      			case '#':
  //      			return "\\#";//$NON-NLS-1$
  	        			
  	        		default: 
  	        			if (((c < 0x0020) || (c > 0x007e))){
  	        				return new StringBuffer()
  							.append('\\')
  							.append('u')
  							.append(toHex((c >> 12) & 0xF))
  							.append(toHex((c >>  8) & 0xF))
  							.append(toHex((c >>  4) & 0xF))
  							.append(toHex( c        & 0xF)).toString();
  	        				
  	        			} else
  	        				return String.valueOf(c);
  	        	}		
  	        }
  
  	private static char toHex(int halfByte) {
  		return HEX_DIGITS[(halfByte & 0xF)];
  	}
  
  	private static String escapeCommentChars(String string) {
  	    StringBuffer sb = new StringBuffer(string.length() + 5);
  	    for (int i = 0; i < string.length(); i++) {
  	      char c = string.charAt(i);
  	      switch (c) {
  	      case '!':
  	        sb.append("\\!"); //$NON-NLS-1$
  	        break;
  	      case '#':
  	        sb.append("\\#"); //$NON-NLS-1$
  	        break;
  	      default:
  	        sb.append(c);
  	      }
  	    }
  	    return sb.toString();
  	}
  
  	private static String escapeLeadingWhiteSpaces(String str) {
  		int firstNonWhiteSpace= findFirstNonWhiteSpace(str);
  		StringBuffer buf= new StringBuffer(firstNonWhiteSpace);
  		for (int i = 0; i < firstNonWhiteSpace; i++) {
  			buf.append('\\');
  		    buf.append(str.charAt(i));
  		}
  		buf.append(str.substring(firstNonWhiteSpace));        
  		return buf.toString();
  	}
  
  	/**
  	 *  returns the length if only whitespaces
  	 */
  	private static int findFirstNonWhiteSpace(String s) {
  		for (int i = 0; i < s.length(); i++) {
  			if (!Character.isWhitespace(s.charAt(i)))
  				return i;
  		}
  		return s.length();
  	}
  
  	private static class KeyValuePairModell extends KeyValuePair implements Comparable {        
  
          int fOffset;
          int fLeadingWhiteSpaces;
  
          public KeyValuePairModell(String key, String value, int offset, int leadingWhiteSpaces) {
              super(key, value);             
              fOffset = offset;
              fLeadingWhiteSpaces = leadingWhiteSpaces;
          }
          
          public KeyValuePairModell(KeyValuePair keyValuePair) {
              super(keyValuePair.fKey, keyValuePair.fValue);
          }
  
          public String getEncodedText(String lineDelimiter) {
  			return PropertyFileDocumentModel.unwindEscapeChars(fKey) + '=' + PropertyFileDocumentModel.unwindValue(fValue) + lineDelimiter;
          }
          
  		public int compareTo(Object o) {
              int counter = 0;
              String key = ((KeyValuePair) o).fKey;
              int minLen = Math.min(key.length(), fKey.length());
              int diffLen = Math.abs(key.length() - fKey.length());
              for (int i=0; i<minLen; i++) {
                  if (key.charAt(i) == fKey.charAt(i)) {
                      counter++;                    
                  } else {
                      break;
                  }
              }            
              return counter - diffLen;
          }
      }
  
      /**
       * anchor element for a list of KeyValuePairs. (it is greater than every
       * other KeyValuePair)
       */
      private static class LastKeyValuePair extends KeyValuePairModell {
  
      	private boolean fNeedsNewLine;
      	
          public LastKeyValuePair(int offset, boolean needsNewLine) {
              super("last", "key", offset, 0); //$NON-NLS-1$ //$NON-NLS-2$
              fNeedsNewLine= needsNewLine;
          }
          public int compareTo(Object o) {
              return 1;
          }
          public boolean needsNewLine() {
          	return fNeedsNewLine;
          }
          public void resetNeedsNewLine() {
          	fNeedsNewLine= false;
          }
      }
  }
  