1 19 20 package org.netbeans.modules.ruby.spi.project.support.rake; 21 22 import java.io.BufferedReader ; 23 import java.io.BufferedWriter ; 24 import java.io.IOException ; 25 import java.io.InputStream ; 26 import java.io.InputStreamReader ; 27 import java.io.OutputStream ; 28 import java.io.OutputStreamWriter ; 29 import java.util.AbstractMap ; 30 import java.util.AbstractSet ; 31 import java.util.ArrayList ; 32 import java.util.Arrays ; 33 import java.util.HashMap ; 34 import java.util.Iterator ; 35 import java.util.LinkedList ; 36 import java.util.List ; 37 import java.util.ListIterator ; 38 import java.util.Map ; 39 import java.util.NoSuchElementException ; 40 import java.util.Set ; 41 42 45 61 public final class EditableProperties extends AbstractMap <String ,String > implements Cloneable { 62 63 65 private final LinkedList <Item> items; 66 67 68 private final Map <String ,Item> itemIndex; 69 70 private final boolean alphabetize; 71 72 private static final String keyValueSeparators = "=: \t\r\n\f"; 73 74 private static final String strictKeyValueSeparators = "=:"; 75 76 private static final String whiteSpaceChars = " \t\r\n\f"; 77 78 private static final String commentChars = "#!"; 79 80 private static final String INDENT = " "; 81 82 private static final int WAITING_FOR_KEY_VALUE = 1; 84 private static final int READING_KEY_VALUE = 2; 85 86 89 public EditableProperties() { 90 this(false); 91 } 92 93 97 public EditableProperties(boolean alphabetize) { 98 this.alphabetize = alphabetize; 99 items = new LinkedList <Item>(); 100 itemIndex = new HashMap <String ,Item>(); 101 } 102 103 109 public EditableProperties(Map <String ,String > map) { 110 this(false); 111 putAll(map); 112 } 113 114 118 private EditableProperties(EditableProperties ep) { 119 alphabetize = ep.alphabetize; 121 items = new LinkedList <Item>(); 122 itemIndex = new HashMap <String ,Item>(ep.items.size() * 4 / 3 + 1); 123 for (Item _i : ep.items) { 124 Item i = (Item) _i.clone(); 125 items.add(i); 126 itemIndex.put(i.getKey(), i); 127 } 128 } 129 130 136 public Set <Map.Entry <String ,String >> entrySet() { 137 return new SetImpl(this); 138 } 139 140 145 public void load(InputStream stream) throws IOException { 146 int state = WAITING_FOR_KEY_VALUE; 147 BufferedReader input = new BufferedReader (new InputStreamReader (stream, "ISO-8859-1")); 148 List <String > tempList = new LinkedList <String >(); 149 String line; 150 int commentLinesCount = 0; 151 while (null != (line = input.readLine())) { 154 tempList.add(line); 155 boolean empty = isEmpty(line); 156 boolean comment = isComment(line); 157 if (state == WAITING_FOR_KEY_VALUE) { 158 if (empty) { 159 createNonKeyItem(tempList); 161 commentLinesCount = 0; 162 } else { 163 if (comment) { 164 commentLinesCount++; 165 } else { 166 state = READING_KEY_VALUE; 167 } 168 } 169 } 170 if (state == READING_KEY_VALUE && !isContinue(line)) { 171 createKeyItem(tempList, commentLinesCount); 173 state = WAITING_FOR_KEY_VALUE; 174 commentLinesCount = 0; 175 } 176 } 177 if (tempList.size() > 0) { 178 if (state == READING_KEY_VALUE) { 179 createKeyItem(tempList, commentLinesCount); 181 } else { 182 createNonKeyItem(tempList); 183 } 184 } 185 } 186 187 192 public void store(OutputStream stream) throws IOException { 193 boolean previousLineWasEmpty = true; 194 BufferedWriter output = new BufferedWriter (new OutputStreamWriter (stream, "ISO-8859-1")); 195 for (Item item : items) { 196 if (item.isSeparate() && !previousLineWasEmpty) { 197 output.newLine(); 198 } 199 String line = null; 200 Iterator <String > it = item.getRawData().iterator(); 201 while (it.hasNext()) { 202 line = it.next(); 203 output.write(line); 204 output.newLine(); 205 } 206 if (line != null) { 207 previousLineWasEmpty = isEmpty(line); 208 } 209 } 210 output.flush(); 211 } 212 213 @Override 214 public String put(String key, String value) { 215 if (key == null || value == null) { 216 throw new NullPointerException (); 217 } 218 Item item = itemIndex.get(key); 219 String result = null; 220 if (item != null) { 221 result = item.getValue(); 222 item.setValue(value); 223 } else { 224 item = new Item(key, value); 225 addItem(item, alphabetize); 226 } 227 return result; 228 } 229 230 236 public String getProperty(String key) { 237 return get(key); 238 } 239 240 247 public String setProperty(String key, String value) { 248 return put(key, value); 249 } 250 251 262 public String setProperty(String key, String [] value) { 263 String result = get(key); 264 if (key == null || value == null) { 265 throw new NullPointerException (); 266 } 267 List <String > valueList = Arrays.asList(value); 268 Item item = itemIndex.get(key); 269 if (item != null) { 270 item.setValue(valueList); 271 } else { 272 addItem(new Item(key, valueList), alphabetize); 273 } 274 return result; 275 } 276 277 287 public String [] getComment(String key) { 288 Item item = itemIndex.get(key); 289 if (item == null) { 290 return new String [0]; 291 } 292 return item.getComment(); 293 } 294 295 307 public void setComment(String key, String [] comment, boolean separate) { 308 Item item = itemIndex.get(key); 310 if (item == null) { 311 throw new IllegalArgumentException ("Cannot set comment for non-existing property "+key); 312 } 313 item.setComment(comment, separate); 314 } 315 316 @Override 317 public Object clone() { 318 return cloneProperties(); 319 } 320 321 325 public EditableProperties cloneProperties() { 326 return new EditableProperties(this); 327 } 328 329 private void createNonKeyItem(List <String > lines) { 331 if (!items.isEmpty()) { 333 Item item = items.getLast(); 334 if (item.getKey() == null) { 335 item.addCommentLines(lines); 337 lines.clear(); 338 return; 339 } 340 } 341 Item item = new Item(lines); 343 addItem(item, false); 344 lines.clear(); 345 } 346 347 private void createKeyItem(List <String > lines, int commentLinesCount) { 350 Item item = new Item(lines.subList(0, commentLinesCount), lines.subList(commentLinesCount, lines.size())); 351 addItem(item, false); 352 lines.clear(); 353 } 354 355 private void addItem(Item item, boolean sort) { 356 String key = item.getKey(); 357 if (sort) { 358 assert key != null; 359 ListIterator <Item> it = items.listIterator(); 360 while (it.hasNext()) { 361 String k = it.next().getKey(); 362 if (k != null && k.compareToIgnoreCase(key) > 0) { 363 it.previous(); 364 it.add(item); 365 itemIndex.put(key, item); 366 return; 367 } 368 } 369 } 370 items.add(item); 371 if (key != null) { 372 itemIndex.put(key, item); 373 } 374 } 375 376 private void removeItem(Item item) { 377 items.remove(item); 378 if (item.getKey() != null) { 379 itemIndex.remove(item.getKey()); 380 } 381 } 382 383 private boolean isContinue(String line) { 385 int index = line.length() - 1; 386 int slashCount = 0; 387 while (index >= 0 && line.charAt(index) == '\\') { 388 slashCount++; 389 index--; 390 } 391 return (slashCount % 2 != 0); 394 } 395 396 private static boolean isComment(String line) { 398 line = trimLeft(line); 399 if (line.length() != 0 && commentChars.indexOf(line.charAt(0)) != -1) { 400 return true; 401 } else { 402 return false; 403 } 404 } 405 406 private static boolean isEmpty(String line) { 408 return trimLeft(line).length() == 0; 409 } 410 411 private static String trimLeft(String line) { 413 int start = 0; 414 while (start < line.length()) { 415 if (whiteSpaceChars.indexOf(line.charAt(start)) == -1) { 416 break; 417 } 418 start++; 419 } 420 return line.substring(start); 421 } 422 423 428 private static class Item implements Cloneable { 429 430 432 private List <String > commentLines; 433 434 436 private List <String > keyValueLines; 437 438 439 private String key; 440 441 442 private String value; 443 444 446 private boolean separate; 447 448 private Item() { 450 } 451 452 456 public Item(List <String > commentLines) { 457 this.commentLines = new ArrayList <String >(commentLines); 458 } 459 460 464 public Item(List <String > commentLines, List <String > keyValueLines) { 465 this.commentLines = new ArrayList <String >(commentLines); 466 this.keyValueLines = new ArrayList <String >(keyValueLines); 467 parse(keyValueLines); 468 } 469 470 473 public Item(String key, String value) { 474 this.key = key; 475 this.value = value; 476 } 477 478 481 public Item(String key, List <String > value) { 482 this.key = key; 483 setValue(value); 484 } 485 486 void addCommentLines(List <String > lines) { 488 assert key == null; 489 commentLines.addAll(lines); 490 } 491 492 public String [] getComment() { 493 String [] res = new String [commentLines.size()]; 494 for (int i = 0; i < res.length; i++) { 495 res[i] = decodeUnicode(commentLines.get(i)); 497 } 498 return res; 499 } 500 501 public void setComment(String [] commentLines, boolean separate) { 502 this.separate = separate; 503 this.commentLines = new ArrayList <String >(commentLines.length); 504 for (int i = 0; i < commentLines.length; i++) { 505 this.commentLines.add(encodeUnicode(commentLines[i])); 507 } 508 } 509 510 public String getKey() { 511 return key; 512 } 513 514 public String getValue() { 515 return value; 516 } 517 518 public void setValue(String value) { 519 this.value = value; 520 keyValueLines = null; 521 } 522 523 public void setValue(List <String > value) { 524 StringBuffer val = new StringBuffer (); 525 List <String > l = new ArrayList <String >(); 526 if (!value.isEmpty()) { 527 l.add(encode(key, true) + "=\\"); Iterator <String > it = value.iterator(); 529 while (it.hasNext()) { 530 String s = it.next(); 531 val.append(s); 532 s = encode(s, false); 533 l.add(it.hasNext() ? INDENT + s + '\\' : INDENT + s); } 535 } else { 536 l.add(encode(key, true) + '='); } 539 this.value = val.toString(); 540 keyValueLines = l; 541 } 542 543 public boolean isSeparate() { 544 return separate; 545 } 546 547 550 public List <String > getRawData() { 551 List <String > l = new ArrayList <String >(); 552 if (commentLines != null) { 553 l.addAll(commentLines); 554 } 555 if (keyValueLines == null) { 556 keyValueLines = new ArrayList <String >(); 557 if (key != null && value != null) { 558 keyValueLines.add(encode(key, true)+"="+encode(value, false)); 559 } 560 } 561 l.addAll(keyValueLines); 562 return l; 563 } 564 565 private void parse(List <String > keyValueLines) { 566 String line = mergeLines(keyValueLines); 568 splitKeyValue(line); 570 } 571 572 private String mergeLines(List <String > lines) { 573 String line = ""; Iterator <String > it = lines.iterator(); 575 while (it.hasNext()) { 576 String l = trimLeft(it.next()); 577 if (it.hasNext()) { 579 assert l.endsWith("\\") : lines; 580 l = l.substring(0, l.length()-1); 581 } 582 line += l; 583 } 584 return line; 585 } 586 587 private void splitKeyValue(String line) { 588 int separatorIndex = 0; 589 while (separatorIndex < line.length()) { 590 char ch = line.charAt(separatorIndex); 591 if (ch == '\\') { 592 separatorIndex++; 594 } else { 595 if (keyValueSeparators.indexOf(ch) != -1) { 596 break; 597 } 598 } 599 separatorIndex++; 600 } 601 key = decode(line.substring(0, separatorIndex)); 602 line = trimLeft(line.substring(separatorIndex)); 603 if (line.length() == 0) { 604 value = ""; 605 return; 606 } 607 if (strictKeyValueSeparators.indexOf(line.charAt(0)) != -1) { 608 line = trimLeft(line.substring(1)); 609 } 610 value = decode(line); 611 } 612 613 private static String decode(String input) { 614 char ch; 615 int len = input.length(); 616 StringBuffer output = new StringBuffer (len); 617 for (int x=0; x<len; x++) { 618 ch = input.charAt(x); 619 if (ch != '\\') { 620 output.append(ch); 621 continue; 622 } 623 x++; 624 if (x==len) { 625 continue; 627 } 628 ch = input.charAt(x); 629 if (ch == 'u') { 630 if (x+5>len) { 631 output.append(input.substring(x-1)); 633 x += 4; 634 continue; 635 } 636 String val = input.substring(x+1, x+5); 637 try { 638 output.append((char)Integer.parseInt(val, 16)); 639 } catch (NumberFormatException e) { 640 output.append(input.substring(x - 1, x + 5)); 642 } 643 x += 4; 644 } else { 645 if (ch == 't') ch = '\t'; 646 else if (ch == 'r') ch = '\r'; 647 else if (ch == 'n') ch = '\n'; 648 else if (ch == 'f') ch = '\f'; 649 output.append(ch); 650 } 651 } 652 return output.toString(); 653 } 654 655 private static String encode(String input, boolean escapeSpace) { 656 int len = input.length(); 657 StringBuffer output = new StringBuffer (len*2); 658 659 for(int x=0; x<len; x++) { 660 char ch = input.charAt(x); 661 switch(ch) { 662 case ' ': 663 if (x == 0 || escapeSpace) { 664 output.append('\\'); 665 } 666 output.append(' '); 667 break; 668 case '\\': 669 output.append("\\\\"); 670 break; 671 case '\t': 672 output.append("\\t"); 673 break; 674 case '\n': 675 output.append("\\n"); 676 break; 677 case '\r': 678 output.append("\\r"); 679 break; 680 case '\f': 681 output.append("\\f"); 682 break; 683 default: 684 if ((ch < 0x0020) || (ch > 0x007e)) { 685 output.append("\\u"); 686 String hex = Integer.toHexString(ch); 687 for (int i = 0; i < 4 - hex.length(); i++) { 688 output.append('0'); 689 } 690 output.append(hex); 691 } else { 692 output.append(ch); 693 } 694 } 695 } 696 return output.toString(); 697 } 698 699 private static String decodeUnicode(String input) { 700 char ch; 701 int len = input.length(); 702 StringBuffer output = new StringBuffer (len); 703 for (int x = 0; x < len; x++) { 704 ch = input.charAt(x); 705 if (ch != '\\') { 706 output.append(ch); 707 continue; 708 } 709 x++; 710 if (x==len) { 711 continue; 713 } 714 ch = input.charAt(x); 715 if (ch == 'u') { 716 if (x+5>len) { 717 output.append(input.substring(x-1)); 719 x += 4; 720 continue; 721 } 722 String val = input.substring(x+1, x+5); 723 try { 724 output.append((char)Integer.parseInt(val, 16)); 725 } catch (NumberFormatException e) { 726 output.append(input.substring(x - 1, x + 5)); 728 } 729 x += 4; 730 } else { 731 output.append(ch); 732 } 733 } 734 return output.toString(); 735 } 736 737 private static String encodeUnicode(String input) { 738 int len = input.length(); 739 StringBuffer output = new StringBuffer (len * 2); 740 for (int x = 0; x < len; x++) { 741 char ch = input.charAt(x); 742 if ((ch < 0x0020) || (ch > 0x007e)) { 743 output.append("\\u"); String hex = Integer.toHexString(ch); 745 for (int i = 0; i < 4 - hex.length(); i++) { 746 output.append('0'); 747 } 748 output.append(hex); 749 } else { 750 output.append(ch); 751 } 752 } 753 return output.toString(); 754 } 755 756 @Override 757 public Object clone() { 758 Item item = new Item(); 759 if (keyValueLines != null) { 760 item.keyValueLines = new ArrayList <String >(keyValueLines); 761 } 762 if (commentLines != null) { 763 item.commentLines = new ArrayList <String >(commentLines); 764 } 765 item.key = key; 766 item.value = value; 767 item.separate = separate; 768 return item; 769 } 770 771 } 772 773 private static class SetImpl extends AbstractSet <Map.Entry <String ,String >> { 774 775 private EditableProperties props; 776 777 public SetImpl(EditableProperties props) { 778 this.props = props; 779 } 780 781 public Iterator <Map.Entry <String ,String >> iterator() { 782 return new IteratorImpl(props); 783 } 784 785 public int size() { 786 return props.items.size(); 787 } 788 789 } 790 791 private static class IteratorImpl implements Iterator <Map.Entry <String ,String >> { 792 793 private final EditableProperties props; 794 private ListIterator <Item> delegate; 795 796 public IteratorImpl(EditableProperties props) { 797 this.props = props; 798 delegate = props.items.listIterator(); 799 } 800 801 public boolean hasNext() { 802 return findNext() != null; 803 } 804 805 public Map.Entry <String ,String > next() { 806 Item item = findNext(); 807 if (item == null) { 808 throw new NoSuchElementException (); 809 } 810 delegate.next(); 811 return new MapEntryImpl(item); 812 } 813 814 public void remove() { 815 delegate.previous(); 816 Item item = findNext(); 817 if (item == null) { 818 throw new IllegalStateException (); 819 } 820 int index = delegate.nextIndex(); 821 props.items.remove(item); 822 props.itemIndex.remove(item.getKey()); 823 delegate = props.items.listIterator(index); 824 } 825 826 private Item findNext() { 827 while (delegate.hasNext()) { 828 Item item = delegate.next(); 829 if (item.getKey() != null && item.getValue() != null) { 830 delegate.previous(); 832 return item; 833 } 834 } 835 return null; 836 } 837 838 } 839 840 private static class MapEntryImpl implements Map.Entry <String ,String > { 841 842 private Item item; 843 844 public MapEntryImpl(Item item) { 845 this.item = item; 846 } 847 848 public String getKey() { 849 return item.getKey(); 850 } 851 852 public String getValue() { 853 return item.getValue(); 854 } 855 856 public String setValue(String value) { 857 String result = item.getValue(); 858 item.setValue(value); 859 return result; 860 } 861 862 } 863 864 } 865 | Popular Tags |