| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| |
| package org.netbeans.modules.properties; |
| |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.util.List; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import org.openide.filesystems.FileObject; |
| import org.openide.loaders.MultiDataObject.Entry; |
| import org.openide.util.WeakListeners; |
| |
| |
| /** |
| * Structure of a bundle of <code>.properties</code> files. |
| * Provides structure of entries (one entry per one .properties file) |
| * for one <code>PropertiesDataObject</code>. |
| * <p> |
| * This structure provides support for sorting entries and fast mapping |
| * of integers to <code>entries</code>. |
| * <p> |
| * The sorting support in this class is a design flaw - |
| * consider it deprecated. |
| * |
| * @author Petr Jiricka |
| */ |
| public class BundleStructure { |
| |
| /** |
| * <code>PropertiesDataObject</code> whose structure is described |
| * by this object |
| */ |
| PropertiesDataObject obj; |
| |
| /** |
| * file entries of the <code>PropertiesDataObject</code>. |
| * The first entry always represents the primary file. |
| * The other entries represent secondary files and are sorted |
| * by the corresponding files' names. |
| * |
| * @see #updateEntries |
| */ |
| private PropertiesFileEntry[] entries; |
| |
| /** |
| * sorted list of non-escaped keys from all entries |
| * |
| * @see #buildKeySet |
| */ |
| private List<String> keyList; |
| |
| /** |
| * Compartor which sorts keylist. |
| * Default set is sort according keys in file order. |
| */ |
| private KeyComparator comparator = new KeyComparator(); |
| |
| /** |
| * registry of <code>PropertyBundleListener</code>s and support |
| * for firing <code>PropertyBundleEvent</code>s. |
| * Methods for registering and notification of listeners delegate to it. |
| */ |
| private PropertyBundleSupport propBundleSupport |
| = new PropertyBundleSupport(this); |
| |
| /** listens to changes on the underlying <code>PropertyDataObject</code> */ |
| private PropertyChangeListener propListener; |
| |
| protected BundleStructure() { |
| obj = null; |
| } |
| /** |
| * Creates a new instance describing a given |
| * <code>PropertiesDataObject</code>. |
| * |
| * @param obj <code>PropertiesDataObject</code> to be desribed |
| */ |
| public BundleStructure(PropertiesDataObject obj) { |
| this.obj = obj; |
| updateEntries(); |
| |
| // Listen on the PropertiesDataObject. |
| propListener = new PropertyChangeListener() { |
| public void propertyChange(PropertyChangeEvent evt) { |
| if (evt.getPropertyName().equals( |
| PropertiesDataObject.PROP_FILES)) { |
| updateEntries(); |
| propBundleSupport.fireBundleStructureChanged(); |
| } |
| } |
| }; |
| obj.addPropertyChangeListener( |
| WeakListeners.propertyChange(propListener, obj)); |
| } |
| |
| |
| /** |
| * Retrieves n-th entry from the list, indexed from <code>0</code>. |
| * The first entry is always the primary entry. |
| * |
| * @param index index of entry to be retrieved, starting at <code>0</code> |
| * @return entry at the specified index; |
| * or <code>null</code> if the index is out of bounds |
| */ |
| public PropertiesFileEntry getNthEntry(int index) { |
| if (entries == null) { |
| notifyEntriesNotInitialized(); |
| } |
| if (index >= 0 && index < entries.length) { |
| return entries[index]; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Retrieves an index of a file entry representing the given file. |
| * |
| * @param fileName simple name (without path and extension) of the |
| * primary or secondary file |
| * @return index of the entry representing a file with the given filename; |
| * or <code>-1</code> if no such entry is found |
| * @exception java.lang.IllegalStateException |
| * if the list of entries has not been initialized yet |
| * @see #getEntryByFileName |
| */ |
| public int getEntryIndexByFileName(String fileName) { |
| if (entries == null) { |
| notifyEntriesNotInitialized(); |
| } |
| for (int i = 0; i < getEntryCount(); i++) { |
| if (entries[i].getFile().getName().equals(fileName)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Retrieves a file entry representing the given file |
| * |
| * @param fileName simple name (excl. path, incl. extension) of the |
| * primary or secondary file |
| * @return entry representing the given file; |
| * or <code>null</code> if not such entry is found |
| * @exception java.lang.IllegalStateException |
| * if the list of entries has not been initialized yet |
| * @see #getEntryIndexByFileName |
| */ |
| public PropertiesFileEntry getEntryByFileName(String fileName) { |
| int index = getEntryIndexByFileName(fileName); |
| return ((index == -1) ? null : entries[index]); |
| } |
| |
| /** |
| * Retrieves number of file entries. |
| * |
| * @return number of file entries |
| * @exception java.lang.IllegalStateException |
| * if the list of entries has not been initialized yet |
| */ |
| public int getEntryCount() { |
| if (entries == null) { |
| notifyEntriesNotInitialized(); |
| } |
| return entries.length; |
| } |
| |
| // Sorted keys management ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| /** |
| * Retrieves all un-escaped keys in bundle. |
| * |
| * @return sorted array of non-escaped keys |
| * @exception java.lang.IllegalStateException |
| * if the list of keys has not been initialized yet |
| * @see #sort |
| */ |
| public String[] getKeys() { |
| if (keyList == null) { |
| notifyKeyListNotInitialized(); |
| } |
| return keyList.toArray(new String[0]); |
| } |
| |
| /** |
| * Retrieves the n-th bundle key from the list, indexed from <code>0</code>. |
| * |
| * @param keyIndex index according to the current order of keys |
| * @return non-escaped key at the given position; |
| * or <code>null</code> if the given index is out of range |
| * @exception java.lang.IllegalStateException |
| * if the list of keys has not been initialized yet |
| */ |
| public String keyAt(int keyIndex) { |
| if (keyList == null) { |
| notifyKeyListNotInitialized(); |
| } |
| if (keyIndex < 0 || keyIndex >= keyList.size()) { |
| return null; |
| } else { |
| return keyList.get(keyIndex); |
| } |
| } |
| |
| /** |
| * Returns the index of the given key within the sorted list of keys |
| * |
| * @param key non-escaped key |
| * @return position of the given key in the bundle; |
| * or <code>-1</code> if the key was not found |
| * @exception java.lang.IllegalStateException |
| * if the list of keys has not been initialized yet |
| */ |
| public int getKeyIndexByName(String key) { |
| if (keyList == null) { |
| notifyKeyListNotInitialized(); |
| } |
| return keyList.indexOf(key); |
| } |
| |
| /** |
| * Finds a free key in the budnle. If the suggested key is not free, |
| * a number is appended to it. |
| */ |
| public String findFreeKey(String keySpec) { |
| if (keyList == null) { |
| notifyKeyListNotInitialized(); |
| } |
| |
| int n = 1; |
| String key = keySpec; |
| while (keyList.contains(key)) { |
| key = keySpec + "_" + n++; |
| } |
| return key; |
| } |
| |
| /** |
| * Retrieves keyIndex-th key in the entryIndex-th entry from the list, |
| * indexed from <code>0</code>. |
| * |
| * @return item for keyIndex-th key in the entryIndex-th entry; |
| * or <code>null</code> if the entry does not contain |
| * the key or entry doesn't exist |
| */ |
| public Element.ItemElem getItem(int entryIndex, int keyIndex) { |
| String key = keyAt(keyIndex); |
| return getItem(entryIndex, key); |
| } |
| |
| /** |
| * Returns a property item having a given key, from a given file entry. |
| * |
| * @param entryIndex index of the file entry to get the item from |
| * @param key key of the property to receive |
| * @return item from the given file entry, having the given key; |
| * or <code>null</code> if one of the following is true: |
| * <ul> |
| * <li>entry index is out of bounds</li> |
| * <li><code>null</code> was passed as a key</li> |
| * <li>the given key was not found in the given entry</li> |
| * <li>structure of the given file entry is not available |
| * because of an error while reading the entry |
| * or because parsing of the file entry was stopped |
| * for some reason</li> |
| * </ul> |
| * @see org.netbeans.modules.properties.Element.ItemElem |
| */ |
| public Element.ItemElem getItem(int entryIndex, String key) { |
| if (key == null) { |
| return null; |
| } |
| PropertiesFileEntry pfe = getNthEntry(entryIndex); |
| if (pfe == null) { |
| return null; |
| } |
| PropertiesStructure ps = pfe.getHandler().getStructure(); |
| if (ps != null) { |
| return ps.getItem(key); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Returns property item of given key from localization corresponding to |
| * given file name. If not found in given file directly then "parent" files |
| * are scanned - the same way as ResourceBundle would work when asked for |
| * locale specific key. |
| * |
| * @param localizationFile name of file entry without extension |
| * corresponding to the desired specific localization |
| * @param key the key of the item in the model. See clarifications |
| * {@link PropertiesStructure#getItem(java.lang.String) here}. |
| * @return a property item if is it possible, otherwise {@code null}. |
| */ |
| public Element.ItemElem getItem(String localizationFile, String key) { |
| int score = 0; // number of same characters in the file name |
| Element.ItemElem item = null; |
| for (int i=0; i < getEntryCount(); i++) { |
| PropertiesFileEntry pfe = getNthEntry(i); |
| if (pfe != null) { |
| String fName = pfe.getFile().getName(); |
| if (localizationFile.startsWith(fName) |
| && (item == null || fName.length() > score)) |
| { // try to find the item in the entry with longest file name |
| // matching (most specific localization) |
| PropertiesStructure ps = pfe.getHandler().getStructure(); |
| if (ps != null) { |
| Element.ItemElem it = ps.getItem(key); |
| if (it != null) { |
| item = it; |
| score = fName.length(); |
| } |
| } |
| } |
| } |
| } |
| return item; |
| } |
| |
| /** |
| * Gets all data for given key from all locales. |
| * @return String[] array of strings - repeating: locale, value, comments |
| */ |
| public String[] getAllData(String key) { |
| List<String> list = null; |
| for (int i=0; i < getEntryCount(); i++) { |
| PropertiesFileEntry pfe = getNthEntry(i); |
| if (pfe != null) { |
| PropertiesStructure ps = pfe.getHandler().getStructure(); |
| if (ps != null) { |
| Element.ItemElem item = ps.getItem(key); |
| if (item != null) { |
| String locale = Util.getLocaleSuffix(pfe); |
| if (list == null) { |
| list = new ArrayList<String>(); |
| } |
| list.add(locale); |
| list.add(item.getValue()); |
| list.add(item.getComment()); |
| } |
| } |
| } |
| } |
| return list != null ? list.toArray(new String[list.size()]) : null; |
| } |
| |
| public void setAllData(String key, String[] data) { |
| // create missing file entries |
| boolean entryCreated = false; |
| for (int i=0; i < data.length; i+=3) { |
| String locale = data[i]; |
| PropertiesFileEntry localeFile = null; |
| for (int j=0; j < getEntryCount(); j++) { |
| PropertiesFileEntry pfe = getNthEntry(j); |
| if (pfe != null && Util.getLocaleSuffix(pfe).equals(locale)) { |
| localeFile = pfe; |
| break; |
| } |
| } |
| if (localeFile == null) { |
| Util.createLocaleFile(obj, locale.substring(1), false); |
| entryCreated = true; |
| } |
| } |
| if (entryCreated) |
| updateEntries(); |
| |
| // add all provided data |
| for (int i=0; i < data.length; i+=3) { |
| String locale = data[i]; |
| for (int j=0; j < getEntryCount(); j++) { |
| PropertiesFileEntry pfe = getNthEntry(j); |
| if (pfe != null && Util.getLocaleSuffix(pfe).equals(locale)) { |
| PropertiesStructure ps = pfe.getHandler().getStructure(); |
| if (ps != null) { |
| Element.ItemElem item = ps.getItem(key); |
| if (item != null) { |
| item.setValue(data[i+1]); |
| item.setComment(data[i+2]); |
| } |
| else { |
| ps.addItem(key, data[i+1], data[i+2]); |
| } |
| } |
| break; |
| } |
| } |
| } |
| |
| // remove superfluous data |
| if (getEntryCount() > data.length/3) { |
| for (int j=0; j < getEntryCount(); j++) { |
| PropertiesFileEntry pfe = getNthEntry(j); |
| PropertiesStructure ps = pfe.getHandler().getStructure(); |
| if (pfe == null || ps == null) continue; |
| |
| boolean found = false; |
| for (int i=0; i < data.length; i+=3) { |
| String locale = data[i]; |
| if (Util.getLocaleSuffix(pfe).equals(locale)) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| ps.deleteItem(key); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns count of all unique keys found in all file entries. |
| * |
| * @return size of a union of keys from all entries |
| * @exception java.lang.IllegalStateException |
| * if the list of keys has not been initialized yet |
| */ |
| public int getKeyCount() { |
| if (keyList != null) { |
| return keyList.size(); |
| } else { |
| notifyKeyListNotInitialized(); |
| return 0; //will not happen |
| } |
| } |
| |
| /** |
| * Adds to or changes an item in specified localization file and its parents. |
| */ |
| public void addItem(String localizationFile, |
| String key, String value, String comment, |
| boolean changeIfExists) |
| { |
| PropertiesStructure[] ps = getRelatedStructures(localizationFile); |
| boolean changed = false; |
| for (int i=0; i < ps.length; i++) { |
| Element.ItemElem item = ps[i].getItem(key); |
| if (item != null) { |
| if (changeIfExists && !changed) { |
| item.setValue(value); |
| item.setComment(comment); |
| changed = true; // change only once - in the most specific set |
| } |
| } |
| else { |
| ps[i].addItem(key, value, comment); |
| changed = true; // change only once - in the most specific set |
| } |
| } |
| } |
| |
| /** |
| * Deletes item with given key from all files of this bundle. |
| */ |
| public void removeItem(String key) { |
| for (int i=0; i < getEntryCount(); i++) { |
| PropertiesFileEntry pfe = getNthEntry(i); |
| if (pfe != null) { |
| PropertiesStructure ps = pfe.getHandler().getStructure(); |
| if (ps != null) { |
| ps.deleteItem(key); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sorts the keylist according the values of entry which index is given |
| * to this method. |
| * |
| * @param index sorts accordinng nth-1 entry values, <code>0</code> means |
| * sort by keys, if less than <code>0</code> it re-compares |
| * keylist with the same un-changed comparator. |
| */ |
| public void sort(int index) { |
| if (index >= 0) { |
| comparator.setIndex(index); |
| } |
| synchronized (this) { |
| Collections.sort(keyList, comparator); |
| } |
| propBundleSupport.fireBundleDataChanged(); |
| } |
| |
| /** |
| * Gets index accoring which is bundle key list sorted. |
| * |
| * @return index, <code>0</code> means according keys, |
| * <code>-1</code> means sorting as in default |
| * properties file |
| */ |
| public int getSortIndex() { |
| return comparator.getIndex(); |
| } |
| |
| /** |
| * Gets current order of sort. |
| * |
| * @return true if ascending, alse descending order |
| * (until sort index is <code>-1</code>, then unsorted) |
| */ |
| public boolean getSortOrder() { |
| return comparator.isAscending(); |
| } |
| |
| PropertiesOpen getOpenSupport() { |
| throw new UnsupportedOperationException("Not yet implemented"); |
| } |
| |
| /** |
| * Builds (or rebuilds) a sorted list of entries of the underlying |
| * <code>PropertiesDataObject<code> and a sorted list of keys gathered |
| * from all the entries. |
| * |
| * @see #entries |
| * @see #keyList |
| */ |
| void updateEntries() { |
| Map<String,PropertiesFileEntry> tm = new TreeMap<String,PropertiesFileEntry>( |
| PropertiesDataObject.getSecondaryFilesComparator()); |
| for (Entry entry : obj.secondaryEntries()) { |
| tm.put(entry.getFile().getName(), (PropertiesFileEntry) entry); |
| } |
| |
| synchronized (this) { |
| // Move the entries. |
| int entriesCount = tm.size(); |
| entries = new PropertiesFileEntry[entriesCount + 1]; |
| entries[0] = (PropertiesFileEntry) obj.getPrimaryEntry(); |
| |
| int index = 0; |
| for (Map.Entry<String,PropertiesFileEntry> mapEntry : tm.entrySet()) { |
| entries[++index] = mapEntry.getValue(); |
| } |
| } |
| buildKeySet(); |
| } |
| |
| /** |
| * Constructs a sorted list of all keys gathered from all entries. |
| * |
| * @see #keyList |
| */ |
| protected synchronized void buildKeySet() { |
| List<String> keyList = new ArrayList<String>() { |
| public boolean equals(Object obj) { |
| if (!(obj instanceof ArrayList)) { |
| return false; |
| } |
| ArrayList list2 = (ArrayList) obj; |
| |
| if (this.size() != list2.size()) { |
| return false; |
| } |
| for (int i = 0; i < this.size(); i++) { |
| if (!this.contains(list2.get(i)) |
| || !list2.contains(this.get(i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| }; |
| |
| //Create interim Set as ArrayList.contains is an expensive operation |
| // and can cause delayes on large property files. |
| // See: #188619 |
| Set interimSet = new HashSet<String>(keyList); |
| // for all entries add all keys |
| int entriesCount = getEntryCount(); |
| for (int index = 0; index < entriesCount; index++) { |
| PropertiesFileEntry entry = getNthEntry(index); |
| if (entry != null) { |
| PropertiesStructure ps = entry.getHandler().getStructure(); |
| if (ps != null) { |
| for (Iterator<Element.ItemElem> it = ps.allItems(); it.hasNext(); ) { |
| Element.ItemElem item = it.next(); |
| if (item == null) { |
| continue; |
| } |
| String key = item.getKey(); |
| if (key != null) { |
| interimSet.add(key); |
| } |
| } |
| } |
| } |
| } |
| keyList.addAll(interimSet); |
| Collections.sort(keyList, comparator); |
| this.keyList = keyList; |
| } |
| |
| /** |
| * Collects PropertyStructure objects that are related for given design time |
| * localization - i.e. the structure corresponding to the given file name |
| * plus all the "parents". Sorted from the most specific. |
| * @param localizationFile name of specific file entry (without extension) |
| */ |
| private PropertiesStructure[] getRelatedStructures(String localizationFile) { |
| List<PropertiesFileEntry> list = null; |
| for (int i=0; i < getEntryCount(); i++) { |
| PropertiesFileEntry pfe = getNthEntry(i); |
| if (pfe != null) { |
| if (localizationFile.startsWith(pfe.getFile().getName()) |
| && pfe.getHandler().getStructure() != null) { |
| if (list == null) { |
| list = new ArrayList<PropertiesFileEntry>(4); |
| } |
| list.add(pfe); |
| } |
| } |
| } |
| if (list == null) { |
| return new PropertiesStructure[] {}; |
| } |
| Collections.sort(list, new Comparator<PropertiesFileEntry>() { |
| public int compare(PropertiesFileEntry pfe1, PropertiesFileEntry pfe2) { |
| return pfe2.getFile().getName().length() - pfe1.getFile().getName().length(); |
| } |
| }); |
| |
| PropertiesStructure[] array = new PropertiesStructure[list.size()]; |
| for (int i=0, n=list.size(); i < n; i++) { |
| array[i] = list.get(i).getHandler().getStructure(); |
| } |
| return array; |
| } |
| |
| boolean isReadOnly() { |
| boolean canWrite = false; |
| for (int i=0; i < getEntryCount(); i++) { |
| PropertiesFileEntry entry = getNthEntry(i); |
| if (entry != null) |
| canWrite |= entry.getFile().canWrite(); |
| } |
| return !canWrite; |
| } |
| |
| /** |
| * Registers a given listener so that it will receive notifications |
| * about changes in a property bundle. |
| * If the given listener is already registered, a duplicite registration |
| * will be performed, so that it will get notifications multiple times. |
| * |
| * @param l listener to be registered |
| * @see #removePropertyBundleListener |
| */ |
| public void addPropertyBundleListener(PropertyBundleListener l) { |
| if (propBundleSupport == null) propBundleSupport = new PropertyBundleSupport(this); |
| propBundleSupport.addPropertyBundleListener(l); |
| } |
| |
| /** |
| * Unregisters a given listener so that it will no more receive |
| * notifications about changes in a property bundle. |
| * If the given listener has been registered multiple times, |
| * only one registration item will be removed. |
| * |
| * @param l the PropertyBundleListener |
| * @see #addPropertyBundleListener |
| */ |
| public void removePropertyBundleListener(PropertyBundleListener l) { |
| propBundleSupport.removePropertyBundleListener(l); |
| } |
| |
| /** |
| * Notifies registered listeners of a change of a single item |
| * in a single file entry. |
| * |
| * @param struct object describing the file entry |
| * @param item changed item (within the entry) |
| * @see #addPropertyBundleListener |
| */ |
| void notifyItemChanged(PropertiesStructure struct, Element.ItemElem item) { |
| propBundleSupport.fireItemChanged( |
| struct.getParent().getEntry().getFile().getName(), |
| item.getKey() |
| ); |
| } |
| |
| void notifyOneFileChanged(FileObject file) { |
| // PENDING - events should be finer |
| // find out whether global key table has changed and fire a change |
| // according to that |
| List oldKeyList = keyList; |
| |
| buildKeySet(); |
| if (!keyList.equals(oldKeyList)) { |
| propBundleSupport.fireBundleDataChanged(); |
| } else { |
| propBundleSupport.fireFileChanged(file.getName()); |
| } |
| } |
| /** |
| * Notifies registered listeners of a change in a single file entry. |
| * Depending whether a list of keys has changed, either an event |
| * for a single file is fired (if the list of keys has remained unchanged) |
| * or a notification of a complex change is fired. |
| * |
| * @param handler handler of an object keeping structure of the modified |
| * file (entry) |
| */ |
| void notifyOneFileChanged(StructHandler handler) { |
| // PENDING - events should be finer |
| // find out whether global key table has changed and fire a change |
| // according to that |
| List oldKeyList = keyList; |
| |
| buildKeySet(); |
| if (!keyList.equals(oldKeyList)) { |
| propBundleSupport.fireBundleDataChanged(); |
| } else { |
| propBundleSupport.fireFileChanged( |
| handler.getEntry().getFile().getName()); |
| } |
| } |
| |
| /** |
| * Notifies registered listeners of a change in a single file entry. |
| * The <code>Map</code> arguments are actually list of items, |
| * each <code>Map</code> entry is a pair <item key, item>. |
| * |
| * @param handler handler of an object keeping structure of the modified |
| * file (entry) |
| * @param itemsChanged list of modified items in the entry |
| * @param itemsAdded list of items added to the entry |
| * @param itemsDeleted list of items removed from the entry |
| */ |
| void notifyOneFileChanged(StructHandler handler, |
| Map<String,Element.ItemElem> itemsChanged, |
| Map<String,Element.ItemElem> itemsAdded, |
| Map<String,Element.ItemElem> itemsDeleted) { |
| // PENDING - events should be finer |
| // find out whether global key table has changed |
| // should use a faster algorithm of building the keyset |
| buildKeySet(); |
| propBundleSupport.fireBundleDataChanged(); |
| } |
| |
| /** |
| * Throws a runtime exception with a message that the list of bundle keys |
| * has not been initialized yet. |
| * |
| * @exception java.lang.IllegalStateException thrown always |
| * @see #buildKeySet |
| */ |
| private void notifyKeyListNotInitialized() { |
| throw new IllegalStateException( |
| "Resource Bundles: KeyList not initialized"); //NOI18N |
| } |
| |
| /** |
| * Throws a runtime exception with a message that the entries |
| * have not been initialized yet. |
| * |
| * @exception java.lang.IllegalStateException thrown always |
| * @see #updateEntries |
| */ |
| private void notifyEntriesNotInitialized() { |
| throw new IllegalStateException( |
| "Resource Bundles: Entries not initialized"); //NOI18N |
| } |
| |
| PropertiesFileEntry[] getEntries () { |
| synchronized (this) { |
| if (entries == null) { |
| return new PropertiesFileEntry[0]; |
| } else { |
| return Arrays.copyOf(entries, entries.length); |
| } |
| } |
| } |
| |
| /** |
| * Comparator which compares keys according which locale (column in table was selected). |
| */ |
| private final class KeyComparator implements Comparator<String> { |
| |
| /** Index of column to compare with. */ |
| private int index; |
| |
| /** Flag if ascending order should be performed. */ |
| private boolean ascending; |
| |
| |
| /** Constructor. */ |
| public KeyComparator() { |
| this.index = -1; |
| ascending = false; |
| } |
| |
| |
| /** |
| * Setter for <code>index</code> property. |
| * ascending -> descending -> primary file key order -> .... |
| * |
| * @param index interval <code>0</code> .. entry count |
| */ |
| public void setIndex(int index) { |
| if (index == -1) { |
| throw new IllegalArgumentException(); |
| } |
| // if same column toggle order |
| if (this.index == index) { |
| if (ascending) { |
| ascending = false; |
| } else { |
| // sort as in properties file |
| index = -1; |
| ascending = true; |
| } |
| } else { |
| ascending = true; |
| } |
| this.index = index; |
| } |
| |
| /** |
| * Getter for <code>index</code> property. |
| * |
| * @return <code>-1</code>..entry count, <code>-1</code> means unsorted |
| * */ |
| public int getIndex() { |
| return index; |
| } |
| |
| /** Getter for <code>ascending</code> property. */ |
| public boolean isAscending() { |
| return ascending; |
| } |
| |
| /** |
| * It's strange as it access just being compared list |
| */ |
| public int compare(String o1, String o2) { |
| String str1; |
| String str2; |
| |
| // sort as in default properties file |
| if (index < 0) { |
| Element.ItemElem item1 = getItem(0, o1); |
| Element.ItemElem item2 = getItem(0, o2); |
| if (item1 != null && item2 != null) { |
| int i1 = item1.getBounds().getBegin().getOffset(); |
| int i2 = item2.getBounds().getBegin().getOffset(); |
| return i1 - i2; |
| } else if (item1 != null) { |
| return -1; |
| } else if (item2 != null) { |
| return 1; |
| } else { |
| /* |
| * None of the keys is in the default (primary) properties |
| * file. Order the files by name. |
| */ |
| str1 = o1; |
| str2 = o2; |
| } |
| } |
| // key column |
| if (index == 0) { |
| str1 = o1; |
| str2 = o2; |
| } else { |
| Element.ItemElem item1 = getItem(index - 1, o1); |
| Element.ItemElem item2 = getItem(index - 1, o2); |
| if (item1 == null) { |
| if (item2 == null) { |
| return 0; |
| } else { |
| return ascending ? 1 : -1; |
| } |
| } else { |
| if (item2 == null) { |
| return ascending ? -1 : 1; |
| } |
| } |
| str1 = item1.getValue(); |
| str2 = item2.getValue(); |
| } |
| |
| if (str1 == null) { |
| if (str2 == null) { |
| return 0; |
| } else { |
| return ascending ? 1 : -1; |
| } |
| } else if (str2 == null) { |
| return ascending ? -1 : 1; |
| } |
| int res = str1.compareToIgnoreCase(str2); |
| |
| return ascending ? res : -res; |
| } |
| |
| } // End of inner class KeyComparator. |
| |
| } |