PageRenderTime 51ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/projects/netbeans-7.3/properties/src/org/netbeans/modules/properties/BundleStructure.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 954 lines | 521 code | 73 blank | 360 comment | 174 complexity | c7f545b024ebe59f2f9365ca2cce5795 MD5 | raw file
  1. /*
  2. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3. *
  4. * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
  5. *
  6. * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  7. * Other names may be trademarks of their respective owners.
  8. *
  9. * The contents of this file are subject to the terms of either the GNU
  10. * General Public License Version 2 only ("GPL") or the Common
  11. * Development and Distribution License("CDDL") (collectively, the
  12. * "License"). You may not use this file except in compliance with the
  13. * License. You can obtain a copy of the License at
  14. * http://www.netbeans.org/cddl-gplv2.html
  15. * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
  16. * specific language governing permissions and limitations under the
  17. * License. When distributing the software, include this License Header
  18. * Notice in each file and include the License file at
  19. * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
  20. * particular file as subject to the "Classpath" exception as provided
  21. * by Oracle in the GPL Version 2 section of the License file that
  22. * accompanied this code. If applicable, add the following below the
  23. * License Header, with the fields enclosed by brackets [] replaced by
  24. * your own identifying information:
  25. * "Portions Copyrighted [year] [name of copyright owner]"
  26. *
  27. * Contributor(s):
  28. *
  29. * The Original Software is NetBeans. The Initial Developer of the Original
  30. * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
  31. * Microsystems, Inc. All Rights Reserved.
  32. *
  33. * If you wish your version of this file to be governed by only the CDDL
  34. * or only the GPL Version 2, indicate your decision by adding
  35. * "[Contributor] elects to include this software in this distribution
  36. * under the [CDDL or GPL Version 2] license." If you do not indicate a
  37. * single choice of license, a recipient has the option to distribute
  38. * your version of this file under either the CDDL, the GPL Version 2 or
  39. * to extend the choice of license to its licensees as provided above.
  40. * However, if you add GPL Version 2 code and therefore, elected the GPL
  41. * Version 2 license, then the option applies only if the new code is
  42. * made subject to such option by the copyright holder.
  43. */
  44. package org.netbeans.modules.properties;
  45. import java.beans.PropertyChangeEvent;
  46. import java.beans.PropertyChangeListener;
  47. import java.util.List;
  48. import java.util.ArrayList;
  49. import java.util.Arrays;
  50. import java.util.Collections;
  51. import java.util.Comparator;
  52. import java.util.HashSet;
  53. import java.util.Iterator;
  54. import java.util.Map;
  55. import java.util.Set;
  56. import java.util.TreeMap;
  57. import org.openide.filesystems.FileObject;
  58. import org.openide.loaders.MultiDataObject.Entry;
  59. import org.openide.util.WeakListeners;
  60. /**
  61. * Structure of a bundle of <code>.properties</code> files.
  62. * Provides structure of entries (one entry per one .properties file)
  63. * for one <code>PropertiesDataObject</code>.
  64. * <p>
  65. * This structure provides support for sorting entries and fast mapping
  66. * of integers to <code>entries</code>.
  67. * <p>
  68. * The sorting support in this class is a design flaw&nbsp;-
  69. * consider it deprecated.
  70. *
  71. * @author Petr Jiricka
  72. */
  73. public class BundleStructure {
  74. /**
  75. * <code>PropertiesDataObject</code> whose structure is described
  76. * by this object
  77. */
  78. PropertiesDataObject obj;
  79. /**
  80. * file entries of the <code>PropertiesDataObject</code>.
  81. * The first entry always represents the primary file.
  82. * The other entries represent secondary files and are sorted
  83. * by the corresponding files' names.
  84. *
  85. * @see #updateEntries
  86. */
  87. private PropertiesFileEntry[] entries;
  88. /**
  89. * sorted list of non-escaped keys from all entries
  90. *
  91. * @see #buildKeySet
  92. */
  93. private List<String> keyList;
  94. /**
  95. * Compartor which sorts keylist.
  96. * Default set is sort according keys in file order.
  97. */
  98. private KeyComparator comparator = new KeyComparator();
  99. /**
  100. * registry of <code>PropertyBundleListener</code>s and support
  101. * for firing <code>PropertyBundleEvent</code>s.
  102. * Methods for registering and notification of listeners delegate to it.
  103. */
  104. private PropertyBundleSupport propBundleSupport
  105. = new PropertyBundleSupport(this);
  106. /** listens to changes on the underlying <code>PropertyDataObject</code> */
  107. private PropertyChangeListener propListener;
  108. protected BundleStructure() {
  109. obj = null;
  110. }
  111. /**
  112. * Creates a new instance describing a given
  113. * <code>PropertiesDataObject</code>.
  114. *
  115. * @param obj <code>PropertiesDataObject</code> to be desribed
  116. */
  117. public BundleStructure(PropertiesDataObject obj) {
  118. this.obj = obj;
  119. updateEntries();
  120. // Listen on the PropertiesDataObject.
  121. propListener = new PropertyChangeListener() {
  122. public void propertyChange(PropertyChangeEvent evt) {
  123. if (evt.getPropertyName().equals(
  124. PropertiesDataObject.PROP_FILES)) {
  125. updateEntries();
  126. propBundleSupport.fireBundleStructureChanged();
  127. }
  128. }
  129. };
  130. obj.addPropertyChangeListener(
  131. WeakListeners.propertyChange(propListener, obj));
  132. }
  133. /**
  134. * Retrieves n-th entry from the list, indexed from <code>0</code>.
  135. * The first entry is always the primary entry.
  136. *
  137. * @param index index of entry to be retrieved, starting at <code>0</code>
  138. * @return entry at the specified index;
  139. * or <code>null</code> if the index is out of bounds
  140. */
  141. public PropertiesFileEntry getNthEntry(int index) {
  142. if (entries == null) {
  143. notifyEntriesNotInitialized();
  144. }
  145. if (index >= 0 && index < entries.length) {
  146. return entries[index];
  147. } else {
  148. return null;
  149. }
  150. }
  151. /**
  152. * Retrieves an index of a file entry representing the given file.
  153. *
  154. * @param fileName simple name (without path and extension) of the
  155. * primary or secondary file
  156. * @return index of the entry representing a file with the given filename;
  157. * or <code>-1</code> if no such entry is found
  158. * @exception java.lang.IllegalStateException
  159. * if the list of entries has not been initialized yet
  160. * @see #getEntryByFileName
  161. */
  162. public int getEntryIndexByFileName(String fileName) {
  163. if (entries == null) {
  164. notifyEntriesNotInitialized();
  165. }
  166. for (int i = 0; i < getEntryCount(); i++) {
  167. if (entries[i].getFile().getName().equals(fileName)) {
  168. return i;
  169. }
  170. }
  171. return -1;
  172. }
  173. /**
  174. * Retrieves a file entry representing the given file
  175. *
  176. * @param fileName simple name (excl. path, incl. extension) of the
  177. * primary or secondary file
  178. * @return entry representing the given file;
  179. * or <code>null</code> if not such entry is found
  180. * @exception java.lang.IllegalStateException
  181. * if the list of entries has not been initialized yet
  182. * @see #getEntryIndexByFileName
  183. */
  184. public PropertiesFileEntry getEntryByFileName(String fileName) {
  185. int index = getEntryIndexByFileName(fileName);
  186. return ((index == -1) ? null : entries[index]);
  187. }
  188. /**
  189. * Retrieves number of file entries.
  190. *
  191. * @return number of file entries
  192. * @exception java.lang.IllegalStateException
  193. * if the list of entries has not been initialized yet
  194. */
  195. public int getEntryCount() {
  196. if (entries == null) {
  197. notifyEntriesNotInitialized();
  198. }
  199. return entries.length;
  200. }
  201. // Sorted keys management ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  202. /**
  203. * Retrieves all un-escaped keys in bundle.
  204. *
  205. * @return sorted array of non-escaped keys
  206. * @exception java.lang.IllegalStateException
  207. * if the list of keys has not been initialized yet
  208. * @see #sort
  209. */
  210. public String[] getKeys() {
  211. if (keyList == null) {
  212. notifyKeyListNotInitialized();
  213. }
  214. return keyList.toArray(new String[0]);
  215. }
  216. /**
  217. * Retrieves the n-th bundle key from the list, indexed from <code>0</code>.
  218. *
  219. * @param keyIndex index according to the current order of keys
  220. * @return non-escaped key at the given position;
  221. * or <code>null</code> if the given index is out of range
  222. * @exception java.lang.IllegalStateException
  223. * if the list of keys has not been initialized yet
  224. */
  225. public String keyAt(int keyIndex) {
  226. if (keyList == null) {
  227. notifyKeyListNotInitialized();
  228. }
  229. if (keyIndex < 0 || keyIndex >= keyList.size()) {
  230. return null;
  231. } else {
  232. return keyList.get(keyIndex);
  233. }
  234. }
  235. /**
  236. * Returns the index of the given key within the sorted list of keys
  237. *
  238. * @param key non-escaped key
  239. * @return position of the given key in the bundle;
  240. * or <code>-1</code> if the key was not found
  241. * @exception java.lang.IllegalStateException
  242. * if the list of keys has not been initialized yet
  243. */
  244. public int getKeyIndexByName(String key) {
  245. if (keyList == null) {
  246. notifyKeyListNotInitialized();
  247. }
  248. return keyList.indexOf(key);
  249. }
  250. /**
  251. * Finds a free key in the budnle. If the suggested key is not free,
  252. * a number is appended to it.
  253. */
  254. public String findFreeKey(String keySpec) {
  255. if (keyList == null) {
  256. notifyKeyListNotInitialized();
  257. }
  258. int n = 1;
  259. String key = keySpec;
  260. while (keyList.contains(key)) {
  261. key = keySpec + "_" + n++;
  262. }
  263. return key;
  264. }
  265. /**
  266. * Retrieves keyIndex-th key in the entryIndex-th entry from the list,
  267. * indexed from <code>0</code>.
  268. *
  269. * @return item for keyIndex-th key in the entryIndex-th entry;
  270. * or <code>null</code> if the entry does not contain
  271. * the key or entry doesn't exist
  272. */
  273. public Element.ItemElem getItem(int entryIndex, int keyIndex) {
  274. String key = keyAt(keyIndex);
  275. return getItem(entryIndex, key);
  276. }
  277. /**
  278. * Returns a property item having a given key, from a given file entry.
  279. *
  280. * @param entryIndex index of the file entry to get the item from
  281. * @param key key of the property to receive
  282. * @return item from the given file entry, having the given key;
  283. * or <code>null</code> if one of the following is true:
  284. * <ul>
  285. * <li>entry index is out of bounds</li>
  286. * <li><code>null</code> was passed as a key</li>
  287. * <li>the given key was not found in the given entry</li>
  288. * <li>structure of the given file entry is not available
  289. * because of an error while reading the entry
  290. * or because parsing of the file entry was stopped
  291. * for some reason</li>
  292. * </ul>
  293. * @see org.netbeans.modules.properties.Element.ItemElem
  294. */
  295. public Element.ItemElem getItem(int entryIndex, String key) {
  296. if (key == null) {
  297. return null;
  298. }
  299. PropertiesFileEntry pfe = getNthEntry(entryIndex);
  300. if (pfe == null) {
  301. return null;
  302. }
  303. PropertiesStructure ps = pfe.getHandler().getStructure();
  304. if (ps != null) {
  305. return ps.getItem(key);
  306. } else {
  307. return null;
  308. }
  309. }
  310. /**
  311. * Returns property item of given key from localization corresponding to
  312. * given file name. If not found in given file directly then "parent" files
  313. * are scanned - the same way as ResourceBundle would work when asked for
  314. * locale specific key.
  315. *
  316. * @param localizationFile name of file entry without extension
  317. * corresponding to the desired specific localization
  318. * @param key the key of the item in the model. See clarifications
  319. * {@link PropertiesStructure#getItem(java.lang.String) here}.
  320. * @return a property item if is it possible, otherwise {@code null}.
  321. */
  322. public Element.ItemElem getItem(String localizationFile, String key) {
  323. int score = 0; // number of same characters in the file name
  324. Element.ItemElem item = null;
  325. for (int i=0; i < getEntryCount(); i++) {
  326. PropertiesFileEntry pfe = getNthEntry(i);
  327. if (pfe != null) {
  328. String fName = pfe.getFile().getName();
  329. if (localizationFile.startsWith(fName)
  330. && (item == null || fName.length() > score))
  331. { // try to find the item in the entry with longest file name
  332. // matching (most specific localization)
  333. PropertiesStructure ps = pfe.getHandler().getStructure();
  334. if (ps != null) {
  335. Element.ItemElem it = ps.getItem(key);
  336. if (it != null) {
  337. item = it;
  338. score = fName.length();
  339. }
  340. }
  341. }
  342. }
  343. }
  344. return item;
  345. }
  346. /**
  347. * Gets all data for given key from all locales.
  348. * @return String[] array of strings - repeating: locale, value, comments
  349. */
  350. public String[] getAllData(String key) {
  351. List<String> list = null;
  352. for (int i=0; i < getEntryCount(); i++) {
  353. PropertiesFileEntry pfe = getNthEntry(i);
  354. if (pfe != null) {
  355. PropertiesStructure ps = pfe.getHandler().getStructure();
  356. if (ps != null) {
  357. Element.ItemElem item = ps.getItem(key);
  358. if (item != null) {
  359. String locale = Util.getLocaleSuffix(pfe);
  360. if (list == null) {
  361. list = new ArrayList<String>();
  362. }
  363. list.add(locale);
  364. list.add(item.getValue());
  365. list.add(item.getComment());
  366. }
  367. }
  368. }
  369. }
  370. return list != null ? list.toArray(new String[list.size()]) : null;
  371. }
  372. public void setAllData(String key, String[] data) {
  373. // create missing file entries
  374. boolean entryCreated = false;
  375. for (int i=0; i < data.length; i+=3) {
  376. String locale = data[i];
  377. PropertiesFileEntry localeFile = null;
  378. for (int j=0; j < getEntryCount(); j++) {
  379. PropertiesFileEntry pfe = getNthEntry(j);
  380. if (pfe != null && Util.getLocaleSuffix(pfe).equals(locale)) {
  381. localeFile = pfe;
  382. break;
  383. }
  384. }
  385. if (localeFile == null) {
  386. Util.createLocaleFile(obj, locale.substring(1), false);
  387. entryCreated = true;
  388. }
  389. }
  390. if (entryCreated)
  391. updateEntries();
  392. // add all provided data
  393. for (int i=0; i < data.length; i+=3) {
  394. String locale = data[i];
  395. for (int j=0; j < getEntryCount(); j++) {
  396. PropertiesFileEntry pfe = getNthEntry(j);
  397. if (pfe != null && Util.getLocaleSuffix(pfe).equals(locale)) {
  398. PropertiesStructure ps = pfe.getHandler().getStructure();
  399. if (ps != null) {
  400. Element.ItemElem item = ps.getItem(key);
  401. if (item != null) {
  402. item.setValue(data[i+1]);
  403. item.setComment(data[i+2]);
  404. }
  405. else {
  406. ps.addItem(key, data[i+1], data[i+2]);
  407. }
  408. }
  409. break;
  410. }
  411. }
  412. }
  413. // remove superfluous data
  414. if (getEntryCount() > data.length/3) {
  415. for (int j=0; j < getEntryCount(); j++) {
  416. PropertiesFileEntry pfe = getNthEntry(j);
  417. PropertiesStructure ps = pfe.getHandler().getStructure();
  418. if (pfe == null || ps == null) continue;
  419. boolean found = false;
  420. for (int i=0; i < data.length; i+=3) {
  421. String locale = data[i];
  422. if (Util.getLocaleSuffix(pfe).equals(locale)) {
  423. found = true;
  424. break;
  425. }
  426. }
  427. if (!found) {
  428. ps.deleteItem(key);
  429. }
  430. }
  431. }
  432. }
  433. /**
  434. * Returns count of all unique keys found in all file entries.
  435. *
  436. * @return size of a union of keys from all entries
  437. * @exception java.lang.IllegalStateException
  438. * if the list of keys has not been initialized yet
  439. */
  440. public int getKeyCount() {
  441. if (keyList != null) {
  442. return keyList.size();
  443. } else {
  444. notifyKeyListNotInitialized();
  445. return 0; //will not happen
  446. }
  447. }
  448. /**
  449. * Adds to or changes an item in specified localization file and its parents.
  450. */
  451. public void addItem(String localizationFile,
  452. String key, String value, String comment,
  453. boolean changeIfExists)
  454. {
  455. PropertiesStructure[] ps = getRelatedStructures(localizationFile);
  456. boolean changed = false;
  457. for (int i=0; i < ps.length; i++) {
  458. Element.ItemElem item = ps[i].getItem(key);
  459. if (item != null) {
  460. if (changeIfExists && !changed) {
  461. item.setValue(value);
  462. item.setComment(comment);
  463. changed = true; // change only once - in the most specific set
  464. }
  465. }
  466. else {
  467. ps[i].addItem(key, value, comment);
  468. changed = true; // change only once - in the most specific set
  469. }
  470. }
  471. }
  472. /**
  473. * Deletes item with given key from all files of this bundle.
  474. */
  475. public void removeItem(String key) {
  476. for (int i=0; i < getEntryCount(); i++) {
  477. PropertiesFileEntry pfe = getNthEntry(i);
  478. if (pfe != null) {
  479. PropertiesStructure ps = pfe.getHandler().getStructure();
  480. if (ps != null) {
  481. ps.deleteItem(key);
  482. }
  483. }
  484. }
  485. }
  486. /**
  487. * Sorts the keylist according the values of entry which index is given
  488. * to this method.
  489. *
  490. * @param index sorts accordinng nth-1 entry values, <code>0</code> means
  491. * sort by keys, if less than <code>0</code> it re-compares
  492. * keylist with the same un-changed comparator.
  493. */
  494. public void sort(int index) {
  495. if (index >= 0) {
  496. comparator.setIndex(index);
  497. }
  498. synchronized (this) {
  499. Collections.sort(keyList, comparator);
  500. }
  501. propBundleSupport.fireBundleDataChanged();
  502. }
  503. /**
  504. * Gets index accoring which is bundle key list sorted.
  505. *
  506. * @return index, <code>0</code> means according keys,
  507. * <code>-1</code> means sorting as in default
  508. * properties file
  509. */
  510. public int getSortIndex() {
  511. return comparator.getIndex();
  512. }
  513. /**
  514. * Gets current order of sort.
  515. *
  516. * @return true if ascending, alse descending order
  517. * (until sort index is <code>-1</code>, then unsorted)
  518. */
  519. public boolean getSortOrder() {
  520. return comparator.isAscending();
  521. }
  522. PropertiesOpen getOpenSupport() {
  523. throw new UnsupportedOperationException("Not yet implemented");
  524. }
  525. /**
  526. * Builds (or rebuilds) a sorted list of entries of the underlying
  527. * <code>PropertiesDataObject<code> and a sorted list of keys gathered
  528. * from all the entries.
  529. *
  530. * @see #entries
  531. * @see #keyList
  532. */
  533. void updateEntries() {
  534. Map<String,PropertiesFileEntry> tm = new TreeMap<String,PropertiesFileEntry>(
  535. PropertiesDataObject.getSecondaryFilesComparator());
  536. for (Entry entry : obj.secondaryEntries()) {
  537. tm.put(entry.getFile().getName(), (PropertiesFileEntry) entry);
  538. }
  539. synchronized (this) {
  540. // Move the entries.
  541. int entriesCount = tm.size();
  542. entries = new PropertiesFileEntry[entriesCount + 1];
  543. entries[0] = (PropertiesFileEntry) obj.getPrimaryEntry();
  544. int index = 0;
  545. for (Map.Entry<String,PropertiesFileEntry> mapEntry : tm.entrySet()) {
  546. entries[++index] = mapEntry.getValue();
  547. }
  548. }
  549. buildKeySet();
  550. }
  551. /**
  552. * Constructs a sorted list of all keys gathered from all entries.
  553. *
  554. * @see #keyList
  555. */
  556. protected synchronized void buildKeySet() {
  557. List<String> keyList = new ArrayList<String>() {
  558. public boolean equals(Object obj) {
  559. if (!(obj instanceof ArrayList)) {
  560. return false;
  561. }
  562. ArrayList list2 = (ArrayList) obj;
  563. if (this.size() != list2.size()) {
  564. return false;
  565. }
  566. for (int i = 0; i < this.size(); i++) {
  567. if (!this.contains(list2.get(i))
  568. || !list2.contains(this.get(i))) {
  569. return false;
  570. }
  571. }
  572. return true;
  573. }
  574. };
  575. //Create interim Set as ArrayList.contains is an expensive operation
  576. // and can cause delayes on large property files.
  577. // See: #188619
  578. Set interimSet = new HashSet<String>(keyList);
  579. // for all entries add all keys
  580. int entriesCount = getEntryCount();
  581. for (int index = 0; index < entriesCount; index++) {
  582. PropertiesFileEntry entry = getNthEntry(index);
  583. if (entry != null) {
  584. PropertiesStructure ps = entry.getHandler().getStructure();
  585. if (ps != null) {
  586. for (Iterator<Element.ItemElem> it = ps.allItems(); it.hasNext(); ) {
  587. Element.ItemElem item = it.next();
  588. if (item == null) {
  589. continue;
  590. }
  591. String key = item.getKey();
  592. if (key != null) {
  593. interimSet.add(key);
  594. }
  595. }
  596. }
  597. }
  598. }
  599. keyList.addAll(interimSet);
  600. Collections.sort(keyList, comparator);
  601. this.keyList = keyList;
  602. }
  603. /**
  604. * Collects PropertyStructure objects that are related for given design time
  605. * localization - i.e. the structure corresponding to the given file name
  606. * plus all the "parents". Sorted from the most specific.
  607. * @param localizationFile name of specific file entry (without extension)
  608. */
  609. private PropertiesStructure[] getRelatedStructures(String localizationFile) {
  610. List<PropertiesFileEntry> list = null;
  611. for (int i=0; i < getEntryCount(); i++) {
  612. PropertiesFileEntry pfe = getNthEntry(i);
  613. if (pfe != null) {
  614. if (localizationFile.startsWith(pfe.getFile().getName())
  615. && pfe.getHandler().getStructure() != null) {
  616. if (list == null) {
  617. list = new ArrayList<PropertiesFileEntry>(4);
  618. }
  619. list.add(pfe);
  620. }
  621. }
  622. }
  623. if (list == null) {
  624. return new PropertiesStructure[] {};
  625. }
  626. Collections.sort(list, new Comparator<PropertiesFileEntry>() {
  627. public int compare(PropertiesFileEntry pfe1, PropertiesFileEntry pfe2) {
  628. return pfe2.getFile().getName().length() - pfe1.getFile().getName().length();
  629. }
  630. });
  631. PropertiesStructure[] array = new PropertiesStructure[list.size()];
  632. for (int i=0, n=list.size(); i < n; i++) {
  633. array[i] = list.get(i).getHandler().getStructure();
  634. }
  635. return array;
  636. }
  637. boolean isReadOnly() {
  638. boolean canWrite = false;
  639. for (int i=0; i < getEntryCount(); i++) {
  640. PropertiesFileEntry entry = getNthEntry(i);
  641. if (entry != null)
  642. canWrite |= entry.getFile().canWrite();
  643. }
  644. return !canWrite;
  645. }
  646. /**
  647. * Registers a given listener so that it will receive notifications
  648. * about changes in a property bundle.
  649. * If the given listener is already registered, a duplicite registration
  650. * will be performed, so that it will get notifications multiple times.
  651. *
  652. * @param l listener to be registered
  653. * @see #removePropertyBundleListener
  654. */
  655. public void addPropertyBundleListener(PropertyBundleListener l) {
  656. if (propBundleSupport == null) propBundleSupport = new PropertyBundleSupport(this);
  657. propBundleSupport.addPropertyBundleListener(l);
  658. }
  659. /**
  660. * Unregisters a given listener so that it will no more receive
  661. * notifications about changes in a property bundle.
  662. * If the given listener has been registered multiple times,
  663. * only one registration item will be removed.
  664. *
  665. * @param l the PropertyBundleListener
  666. * @see #addPropertyBundleListener
  667. */
  668. public void removePropertyBundleListener(PropertyBundleListener l) {
  669. propBundleSupport.removePropertyBundleListener(l);
  670. }
  671. /**
  672. * Notifies registered listeners of a change of a single item
  673. * in a single file entry.
  674. *
  675. * @param struct object describing the file entry
  676. * @param item changed item (within the entry)
  677. * @see #addPropertyBundleListener
  678. */
  679. void notifyItemChanged(PropertiesStructure struct, Element.ItemElem item) {
  680. propBundleSupport.fireItemChanged(
  681. struct.getParent().getEntry().getFile().getName(),
  682. item.getKey()
  683. );
  684. }
  685. void notifyOneFileChanged(FileObject file) {
  686. // PENDING - events should be finer
  687. // find out whether global key table has changed and fire a change
  688. // according to that
  689. List oldKeyList = keyList;
  690. buildKeySet();
  691. if (!keyList.equals(oldKeyList)) {
  692. propBundleSupport.fireBundleDataChanged();
  693. } else {
  694. propBundleSupport.fireFileChanged(file.getName());
  695. }
  696. }
  697. /**
  698. * Notifies registered listeners of a change in a single file entry.
  699. * Depending whether a list of keys has changed, either an event
  700. * for a single file is fired (if the list of keys has remained unchanged)
  701. * or a notification of a complex change is fired.
  702. *
  703. * @param handler handler of an object keeping structure of the modified
  704. * file (entry)
  705. */
  706. void notifyOneFileChanged(StructHandler handler) {
  707. // PENDING - events should be finer
  708. // find out whether global key table has changed and fire a change
  709. // according to that
  710. List oldKeyList = keyList;
  711. buildKeySet();
  712. if (!keyList.equals(oldKeyList)) {
  713. propBundleSupport.fireBundleDataChanged();
  714. } else {
  715. propBundleSupport.fireFileChanged(
  716. handler.getEntry().getFile().getName());
  717. }
  718. }
  719. /**
  720. * Notifies registered listeners of a change in a single file entry.
  721. * The <code>Map</code> arguments are actually list of items,
  722. * each <code>Map</code> entry is a pair &lt;item&nbsp;key, item&gt;.
  723. *
  724. * @param handler handler of an object keeping structure of the modified
  725. * file (entry)
  726. * @param itemsChanged list of modified items in the entry
  727. * @param itemsAdded list of items added to the entry
  728. * @param itemsDeleted list of items removed from the entry
  729. */
  730. void notifyOneFileChanged(StructHandler handler,
  731. Map<String,Element.ItemElem> itemsChanged,
  732. Map<String,Element.ItemElem> itemsAdded,
  733. Map<String,Element.ItemElem> itemsDeleted) {
  734. // PENDING - events should be finer
  735. // find out whether global key table has changed
  736. // should use a faster algorithm of building the keyset
  737. buildKeySet();
  738. propBundleSupport.fireBundleDataChanged();
  739. }
  740. /**
  741. * Throws a runtime exception with a message that the list of bundle keys
  742. * has not been initialized yet.
  743. *
  744. * @exception java.lang.IllegalStateException thrown always
  745. * @see #buildKeySet
  746. */
  747. private void notifyKeyListNotInitialized() {
  748. throw new IllegalStateException(
  749. "Resource Bundles: KeyList not initialized"); //NOI18N
  750. }
  751. /**
  752. * Throws a runtime exception with a message that the entries
  753. * have not been initialized yet.
  754. *
  755. * @exception java.lang.IllegalStateException thrown always
  756. * @see #updateEntries
  757. */
  758. private void notifyEntriesNotInitialized() {
  759. throw new IllegalStateException(
  760. "Resource Bundles: Entries not initialized"); //NOI18N
  761. }
  762. PropertiesFileEntry[] getEntries () {
  763. synchronized (this) {
  764. if (entries == null) {
  765. return new PropertiesFileEntry[0];
  766. } else {
  767. return Arrays.copyOf(entries, entries.length);
  768. }
  769. }
  770. }
  771. /**
  772. * Comparator which compares keys according which locale (column in table was selected).
  773. */
  774. private final class KeyComparator implements Comparator<String> {
  775. /** Index of column to compare with. */
  776. private int index;
  777. /** Flag if ascending order should be performed. */
  778. private boolean ascending;
  779. /** Constructor. */
  780. public KeyComparator() {
  781. this.index = -1;
  782. ascending = false;
  783. }
  784. /**
  785. * Setter for <code>index</code> property.
  786. * ascending -&gt; descending -&gt; primary file key order -&gt; ....
  787. *
  788. * @param index interval <code>0</code> .. entry count
  789. */
  790. public void setIndex(int index) {
  791. if (index == -1) {
  792. throw new IllegalArgumentException();
  793. }
  794. // if same column toggle order
  795. if (this.index == index) {
  796. if (ascending) {
  797. ascending = false;
  798. } else {
  799. // sort as in properties file
  800. index = -1;
  801. ascending = true;
  802. }
  803. } else {
  804. ascending = true;
  805. }
  806. this.index = index;
  807. }
  808. /**
  809. * Getter for <code>index</code> property.
  810. *
  811. * @return <code>-1</code>..entry count, <code>-1</code> means unsorted
  812. * */
  813. public int getIndex() {
  814. return index;
  815. }
  816. /** Getter for <code>ascending</code> property. */
  817. public boolean isAscending() {
  818. return ascending;
  819. }
  820. /**
  821. * It's strange as it access just being compared list
  822. */
  823. public int compare(String o1, String o2) {
  824. String str1;
  825. String str2;
  826. // sort as in default properties file
  827. if (index < 0) {
  828. Element.ItemElem item1 = getItem(0, o1);
  829. Element.ItemElem item2 = getItem(0, o2);
  830. if (item1 != null && item2 != null) {
  831. int i1 = item1.getBounds().getBegin().getOffset();
  832. int i2 = item2.getBounds().getBegin().getOffset();
  833. return i1 - i2;
  834. } else if (item1 != null) {
  835. return -1;
  836. } else if (item2 != null) {
  837. return 1;
  838. } else {
  839. /*
  840. * None of the keys is in the default (primary) properties
  841. * file. Order the files by name.
  842. */
  843. str1 = o1;
  844. str2 = o2;
  845. }
  846. }
  847. // key column
  848. if (index == 0) {
  849. str1 = o1;
  850. str2 = o2;
  851. } else {
  852. Element.ItemElem item1 = getItem(index - 1, o1);
  853. Element.ItemElem item2 = getItem(index - 1, o2);
  854. if (item1 == null) {
  855. if (item2 == null) {
  856. return 0;
  857. } else {
  858. return ascending ? 1 : -1;
  859. }
  860. } else {
  861. if (item2 == null) {
  862. return ascending ? -1 : 1;
  863. }
  864. }
  865. str1 = item1.getValue();
  866. str2 = item2.getValue();
  867. }
  868. if (str1 == null) {
  869. if (str2 == null) {
  870. return 0;
  871. } else {
  872. return ascending ? 1 : -1;
  873. }
  874. } else if (str2 == null) {
  875. return ascending ? -1 : 1;
  876. }
  877. int res = str1.compareToIgnoreCase(str2);
  878. return ascending ? res : -res;
  879. }
  880. } // End of inner class KeyComparator.
  881. }