--- a/properties/src/org/netbeans/modules/properties/BundleEditPanel.form +++ a/properties/src/org/netbeans/modules/properties/BundleEditPanel.form @@ -1,11 +1,15 @@ -
+ + + + + --- a/properties/src/org/netbeans/modules/properties/BundleEditPanel.java +++ a/properties/src/org/netbeans/modules/properties/BundleEditPanel.java @@ -73,8 +73,11 @@ public class BundleEditPanel extends JPanel implements PropertyChangeListener { /** PropertiesDataObject this panel presents. */ - private PropertiesDataObject obj; - +// private PropertiesDataObject obj; + + /** */ + private BundleStructure structure; + /** Document listener for value and comment textareas. */ private DocumentListener listener; @@ -88,8 +91,10 @@ private int lastSelectedColumn; /** Creates new form BundleEditPanel */ + @Deprecated public BundleEditPanel(final PropertiesDataObject obj, PropertiesTableModel propTableModel) { - this.obj = obj; +// this.obj = obj; + this.structure = obj.getBundleStructure(); initComponents(); initAccessibility(); @@ -168,6 +173,86 @@ } // End of constructor. + /** Creates new form BundleEditPanel */ + public BundleEditPanel(final BundleStructure structure, PropertiesTableModel propTableModel) { + this.structure = structure; + + initComponents(); + initAccessibility(); + initSettings(); + + // Sets table column model. + table.setColumnModel(new TableViewColumnModel()); + + // Sets custom table header renderer (with sorting indicators). + JTableHeader header = table.getTableHeader(); + header.setDefaultRenderer( + new TableViewHeaderRenderer(structure, header.getDefaultRenderer())); + + // Sets table model. + table.setModel(propTableModel); + + // Sets table cell editor. + JTextField textField = new JTextField(); + // Force the document to accept newlines. The textField doesn't like + // it, but the same document is used by the textValue text + // area that must accept newlines. + textField.getDocument().putProperty("filterNewlines", Boolean.FALSE); // NOI18N + textField.setBorder(new LineBorder(Color.black)); + textField.getAccessibleContext().setAccessibleName(NbBundle.getBundle(BundleEditPanel.class).getString("ACSN_CellEditor")); + textField.getAccessibleContext().setAccessibleDescription(NbBundle.getBundle(BundleEditPanel.class).getString("ACSD_CellEditor")); + listener = new ModifiedListener(); + table.setDefaultEditor(PropertiesTableModel.StringPair.class, + new PropertiesTableCellEditor(textField, textComment, textValue, valueLabel, listener)); + + // Sets renderer. + table.setDefaultRenderer(PropertiesTableModel.StringPair.class, new TableViewRenderer()); + + updateAddButton(); + + // property change listener - listens to editing state of the table + table.addPropertyChangeListener(new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("tableCellEditor")) { // NOI18N + updateEnabled(); + } else if (evt.getPropertyName().equals("model")) { // NOI18N + updateAddButton(); + } + } + }); + + // listens on clikcs on table header, detects column and sort accordingly to chosen one + table.getTableHeader().addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + TableColumnModel colModel = table.getColumnModel(); + int columnModelIndex = colModel.getColumnIndexAtX(e.getX()); + // No column was clicked. + if (columnModelIndex < 0) { + return; + } + int modelIndex = colModel.getColumn(columnModelIndex).getModelIndex(); + // not detected column + if (modelIndex < 0) { + return; + } + structure.sort(modelIndex); + } + }); + + + table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent evt) { + final boolean correctCellSelection = !selectionUpdateDisabled; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + updateSelection(correctCellSelection); + } + }); + } + }); + + } // End of constructor. /** Stops editing if editing is in run. */ protected void stopEditing() { @@ -229,7 +314,7 @@ if (ex == null) { return; } - String [] keys = obj.getBundleStructure().getKeys(); + String [] keys = structure.getKeys(); int idx; for (idx = 0; idx < keys.length; idx++) { String key = keys[idx]; @@ -248,7 +333,7 @@ } lastSelectedColumn = column; - BundleStructure structure = obj.getBundleStructure(); +// BundleStructure structure = obj.getBundleStructure(); removeButton.setEnabled((row >= 0) && (!structure.isReadOnly())); String value; String comment; @@ -281,14 +366,13 @@ } private void updateAddButton() { - addButton.setEnabled(!obj.getBundleStructure().isReadOnly()); + addButton.setEnabled(!structure.isReadOnly()); } /** Returns the main table with all values */ public JTable getTable() { return table; } - /** Initializes settings variable. */ private void initSettings() { @@ -511,10 +595,11 @@ if (DialogDisplayer.getDefault().notify(msg).equals(NotifyDescriptor.OK_OPTION)) { try { // Starts "atomic" acion for special undo redo manager of open support. - obj.getOpenSupport().atomicUndoRedoFlag = new Object(); +// TODO XXX +// obj.getOpenSupport().atomicUndoRedoFlag = new Object(); - for (int i=0; i < obj.getBundleStructure().getEntryCount(); i++) { - PropertiesFileEntry entry = obj.getBundleStructure().getNthEntry(i); + for (int i=0; i < structure.getEntryCount(); i++) { + PropertiesFileEntry entry = structure.getNthEntry(i); if (entry != null) { PropertiesStructure ps = entry.getHandler().getStructure(); if (ps != null) { @@ -524,7 +609,8 @@ } } finally { // finishes "atomic" undo redo action for special undo redo manager of open support - obj.getOpenSupport().atomicUndoRedoFlag = null; +// TODO XXX +// obj.getOpenSupport().atomicUndoRedoFlag = null; } } }//GEN-LAST:event_removeButtonActionPerformed @@ -563,11 +649,12 @@ selectionUpdateDisabled = true; // Starts "atomic" acion for special undo redo manager of open support. - obj.getOpenSupport().atomicUndoRedoFlag = new Object(); +// TODO XXX +// obj.getOpenSupport().atomicUndoRedoFlag = new Object(); // add key to all entries - for (int i=0; i < obj.getBundleStructure().getEntryCount(); i++) { - PropertiesFileEntry entry = obj.getBundleStructure().getNthEntry(i); + for (int i=0; i < structure.getEntryCount(); i++) { + PropertiesFileEntry entry = structure.getNthEntry(i); if (entry != null && !entry.getHandler().getStructure().addItem(key, value, comment)) { NotifyDescriptor.Message msg = new NotifyDescriptor.Message( @@ -586,7 +673,8 @@ } } finally { // Finishes "atomic" undo redo action for special undo redo manager of open support. - obj.getOpenSupport().atomicUndoRedoFlag = null; +// TODO XXX +// obj.getOpenSupport().atomicUndoRedoFlag = null; selectionUpdateDisabled = false; } @@ -600,7 +688,7 @@ PropertiesRequestProcessor.getInstance().post(new Runnable() { public void run() { // Find indexes. - int rowIndex = obj.getBundleStructure().getKeyIndexByName(key); + int rowIndex = structure.getKeyIndexByName(key); if((rowIndex != -1)) { final int row = rowIndex; @@ -669,13 +757,20 @@ private static final String SORT_ASC_ICON = ICON_PKG + "columnSortedAsc.gif"; //NOI18N private static final String SORT_DESC_ICON = ICON_PKG + "columnSortedDesc.gif"; //NOI18N - private final PropertiesDataObject propDataObj; + private final BundleStructure bundleStructure; private final TableCellRenderer origRenderer; private ImageIcon iconSortAsc, iconSortDesc; - + + @Deprecated TableViewHeaderRenderer(PropertiesDataObject propDataObj, TableCellRenderer origRenderer) { - this.propDataObj = propDataObj; + bundleStructure = propDataObj.getBundleStructure(); + this.origRenderer = origRenderer; + } + + TableViewHeaderRenderer(BundleStructure bundleStructure, + TableCellRenderer origRenderer) { + this.bundleStructure = bundleStructure; this.origRenderer = origRenderer; } @@ -690,7 +785,7 @@ if (comp instanceof JLabel) { JLabel label = (JLabel) comp; - BundleStructure bundleStruct = propDataObj.getBundleStructure(); + BundleStructure bundleStruct = bundleStructure; int sortIndex = table.convertColumnIndexToView( bundleStruct.getSortIndex()); if (column == sortIndex) { @@ -995,7 +1090,8 @@ } private void documentModified() { - obj.setModified(true); + ((PropertiesTableModel)table.getModel()).getFileEntry(table.getEditingColumn()).getDataObject().setModified(true); +// obj.setModified(true); } } --- a/properties/src/org/netbeans/modules/properties/BundleStructure.java +++ a/properties/src/org/netbeans/modules/properties/BundleStructure.java @@ -44,6 +44,7 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.io.Serializable; import java.util.List; import java.util.ArrayList; import java.util.Collections; @@ -51,6 +52,7 @@ import java.util.Iterator; import java.util.Map; import java.util.TreeMap; +import org.openide.filesystems.FileObject; import org.openide.loaders.MultiDataObject.Entry; import org.openide.util.WeakListeners; @@ -69,7 +71,7 @@ * @author Petr Jiricka */ public class BundleStructure { - + /** * PropertiesDataObject whose structure is described * by this object @@ -109,7 +111,10 @@ /** listens to changes on the underlying PropertyDataObject */ private PropertyChangeListener propListener; - + + protected BundleStructure() { + obj = null; + } /** * Creates a new instance describing a given * PropertiesDataObject. @@ -543,6 +548,10 @@ return comparator.isAscending(); } + PropertiesOpen getOpenSupport() { + throw new UnsupportedOperationException("Not yet implemented"); + } + /** * Builds (or rebuilds) a sorted list of entries of the underlying * PropertiesDataObject and a sorted list of keys gathered @@ -551,7 +560,7 @@ * @see #entries * @see #keyList */ - private void updateEntries() { + void updateEntries() { Map tm = new TreeMap( PropertiesDataObject.getSecondaryFilesComparator()); for (Entry entry : obj.secondaryEntries()) { @@ -577,7 +586,7 @@ * * @see #keyList */ - private synchronized void buildKeySet() { + protected synchronized void buildKeySet() { List keyList = new ArrayList() { public boolean equals(Object obj) { if (!(obj instanceof ArrayList)) { @@ -602,16 +611,18 @@ int entriesCount = getEntryCount(); for (int index = 0; index < entriesCount; index++) { PropertiesFileEntry entry = getNthEntry(index); - PropertiesStructure ps = entry.getHandler().getStructure(); - if (ps != null) { - for (Iterator it = ps.allItems(); it.hasNext(); ) { - Element.ItemElem item = it.next(); - if (item == null) { - continue; - } - String key = item.getKey(); - if (key != null && !(keyList.contains(key))) { - keyList.add(item.getKey()); + if (entry != null) { + PropertiesStructure ps = entry.getHandler().getStructure(); + if (ps != null) { + for (Iterator it = ps.allItems(); it.hasNext(); ) { + Element.ItemElem item = it.next(); + if (item == null) { + continue; + } + String key = item.getKey(); + if (key != null && !(keyList.contains(key))) { + keyList.add(item.getKey()); + } } } } @@ -676,6 +687,7 @@ * @see #removePropertyBundleListener */ public void addPropertyBundleListener(PropertyBundleListener l) { + if (propBundleSupport == null) propBundleSupport = new PropertyBundleSupport(this); propBundleSupport.addPropertyBundleListener(l); } @@ -707,6 +719,19 @@ ); } + 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 --- a/properties/src/org/netbeans/modules/properties/MultiBundleStructure.java +++ a/properties/src/org/netbeans/modules/properties/MultiBundleStructure.java @@ -0,0 +1,292 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2008 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2008 Sun Microsystems, Inc. + */ +package org.netbeans.modules.properties; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import org.openide.filesystems.FileObject; +import org.openide.loaders.DataObject; +import org.openide.loaders.DataObjectNotFoundException; +import org.openide.util.Exceptions; + +/** + * + * @author alexeybutenko + */ +class MultiBundleStructure extends BundleStructure implements Serializable { + + private transient FileObject[] files; + private transient PropertiesFileEntry primaryEntry; + private String baseName; + + /** Generated Serialized Version UID. */ + static final long serialVersionUID = 7501232754255253334L; + + private transient PropertiesOpen openSupport; + /** Lock used for synchronization of openSupport instance creation */ + private final transient Object OPEN_SUPPORT_LOCK = new Object(); + + + protected MultiBundleStructure() { +// super(); +// files = null; +// primaryEntry = null; +// baseName = null; + } + + public MultiBundleStructure(PropertiesDataObject obj) { +// super(obj); + this.obj = obj; + baseName = Util.getBaseName(obj.getName()); + } + + /** + * + * TODO XXX PENDING Check it out + * + */ + private void findEntries() { + try { + if (obj == null) return; + if (!obj.isValid()) { + if (files.length == 1) { + primaryEntry = null; + obj = null; + files = null; + return; + } + } else { + obj = Util.findPrimaryDataObject(obj); + primaryEntry = (PropertiesFileEntry) obj.getPrimaryEntry(); + } + FileObject primary = primaryEntry.getFile(); + FileObject parent = primary.getParent(); + List listFileObjects = new ArrayList(); + String fName; + FileObject oldCandidate; + for (FileObject file : parent.getChildren()) { + fName = file.getName(); + if (fName.equals(baseName) && file.isValid()) { + listFileObjects.add(0,file); + } + if (fName.indexOf(baseName) != -1) { + int index = fName.indexOf(PropertiesDataLoader.PRB_SEPARATOR_CHAR); + if (index == baseName.length()) { + oldCandidate = null; + while (index != -1) { + FileObject candidate = file; + if (candidate != null && isValidLocaleSuffix(fName.substring(index)) && oldCandidate == null && file.isValid()) { + listFileObjects.add(candidate); + oldCandidate = candidate; + } + index = fName.indexOf(PropertiesDataLoader.PRB_SEPARATOR_CHAR, index + 1); + } + } + } + } + files = new FileObject[listFileObjects.size()]; + int index = 0; + for (FileObject file : listFileObjects) { + files[index++] = file; + } + primaryEntry = getNthEntry(0); + obj = (PropertiesDataObject) primaryEntry.getDataObject(); + } catch (DataObjectNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + } + + void updateEntries() { + findEntries(); + if (files != null) { + buildKeySet(); + } + } + + @Override + public PropertiesFileEntry getNthEntry(int index) { + if (files == null) { + return super.getNthEntry(index); +// notifyEntriesNotInitialized(); + } + if (index >= 0 && index < files.length) { + try { + return (PropertiesFileEntry) ((PropertiesDataObject) DataObject.find(files[index])).getPrimaryEntry(); + } catch (DataObjectNotFoundException ex) { + Exceptions.printStackTrace(ex); + return null; + } + } 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 -1 if no such entry is found + * @exception java.lang.IllegalStateException + * if the list of entries has not been initialized yet + * @see #getEntryByFileName + */ + @Override + public int getEntryIndexByFileName(String fileName) { + if (files == null) { + notifyEntriesNotInitialized(); + } + for (int i = 0; i < getEntryCount(); i++) { + if (files[i].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 null if not such entry is found + * @exception java.lang.IllegalStateException + * if the list of entries has not been initialized yet + * @see #getEntryIndexByFileName + */ + @Override + public PropertiesFileEntry getEntryByFileName(String fileName) { + int index = getEntryIndexByFileName(fileName); + try { + return (index == -1) ? null : (PropertiesFileEntry) ((PropertiesDataObject) DataObject.find(files[index])).getPrimaryEntry(); + } catch (DataObjectNotFoundException ex) { + Exceptions.printStackTrace(ex); + return null; + } + } + + /** + * Retrieves number of file entries. + * + * @return number of file entries + * @exception java.lang.IllegalStateException + * if the list of entries has not been initialized yet + */ + @Override + public int getEntryCount() { + if (files == null) { + return 0;//super.getEntryCount(); +// notifyEntriesNotInitialized(); + } + return files.length; + } + + /** + * 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 + } + + private static boolean isValidLocaleSuffix(String s) { + // first char is _ + int n = s.length(); + String s1; + // check first suffix - language (two chars) + if (n == 3 || (n > 3 && s.charAt(3) == PropertiesDataLoader.PRB_SEPARATOR_CHAR)) { + s1 = s.substring(1, 3).toLowerCase(); + // language must be followed by a valid country suffix or no suffix + } else { + return false; + } + // check second suffix - country (two chars) + String s2; + if (n == 3) { + s2 = null; + } else if (n == 6 || (n > 6 && s.charAt(6) == PropertiesDataLoader.PRB_SEPARATOR_CHAR)) { + s2 = s.substring(4, 6).toUpperCase(); + // country may be followed by whatever additional suffix + } else { + return false; + } + + Set knownLanguages = new HashSet(Arrays.asList(Locale.getISOLanguages())); + if (!knownLanguages.contains(s1)) { + return false; + } + + if (s2 != null) { + Set knownCountries = new HashSet(Arrays.asList(Locale.getISOCountries())); + if (!knownCountries.contains(s2)) { + return false; + } + } + return true; + } + @Override + public PropertiesOpen getOpenSupport() { + synchronized (OPEN_SUPPORT_LOCK) { + if (openSupport == null) { + openSupport = new PropertiesOpen(this); + } + return openSupport; + } + } + + @Override + public int getKeyCount() { + try { + return super.getKeyCount(); + } catch (IllegalStateException ie) { + return 0; + } + } +} --- a/properties/src/org/netbeans/modules/properties/PropertiesDataLoader.java +++ a/properties/src/org/netbeans/modules/properties/PropertiesDataLoader.java @@ -87,6 +87,9 @@ /** */ private static Set knownCountries; + /** */ + private static boolean nestedView = false; + /** Creates new PropertiesDataLoader. */ public PropertiesDataLoader() { super("org.netbeans.modules.properties.PropertiesDataObject"); // NOI18N @@ -153,14 +156,16 @@ * corresponding to an existing file */ String fName = fo.getName(); - int index = fName.indexOf(PRB_SEPARATOR_CHAR); - while (index != -1) { - FileObject candidate = fo.getParent().getFileObject( - fName.substring(0, index), fo.getExt()); - if (candidate != null && isValidLocaleSuffix(fName.substring(index))) { - return candidate; + if (nestedView) { + int index = fName.indexOf(PRB_SEPARATOR_CHAR); + while (index != -1) { + FileObject candidate = fo.getParent().getFileObject( + fName.substring(0, index), fo.getExt()); + if (candidate != null && isValidLocaleSuffix(fName.substring(index))) { + return candidate; + } + index = fName.indexOf(PRB_SEPARATOR_CHAR, index + 1); } - index = fName.indexOf(PRB_SEPARATOR_CHAR, index + 1); } return fo; } else { --- a/properties/src/org/netbeans/modules/properties/PropertiesDataNode.java +++ a/properties/src/org/netbeans/modules/properties/PropertiesDataNode.java @@ -60,7 +60,6 @@ import org.openide.loaders.DataFolder; import org.openide.loaders.DataNode; import org.openide.loaders.DataObject; -import org.openide.loaders.FileEntry; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.NodeTransfer; @@ -103,7 +102,6 @@ public PropertiesDataNode(DataObject dataObject, Children children) { super(dataObject, children); setIconBaseWithExtension("org/netbeans/modules/properties/propertiesObject.png"); // NOI18N - dataObjectListener = new NameUpdater(); dataObject.addPropertyChangeListener( WeakListeners.propertyChange(dataObjectListener, dataObject)); @@ -321,14 +319,15 @@ } // End of NewLocaleType class. private static boolean containsLocale(PropertiesDataObject propertiesDataObject, Locale locale) { - FileObject file = propertiesDataObject.getPrimaryFile(); + FileObject file = propertiesDataObject.getBundleStructure().getNthEntry(0).getFile(); +// FileObject file = propertiesDataObject.getPrimaryFile(); String newName = file.getName() + PropertiesDataLoader.PRB_SEPARATOR_CHAR + locale; - Iterator it = propertiesDataObject.secondaryEntries().iterator(); - while (it.hasNext()) { - FileObject f = ((FileEntry)it.next()).getFile(); + BundleStructure structure = propertiesDataObject.getBundleStructure(); + for (int i = 0; i file.getName().length()) file = f; - } + } return file.getName().equals(newName); } } --- a/properties/src/org/netbeans/modules/properties/PropertiesDataObject.java +++ a/properties/src/org/netbeans/modules/properties/PropertiesDataObject.java @@ -38,10 +38,7 @@ * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ - - package org.netbeans.modules.properties; - import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -60,10 +57,12 @@ import org.openide.loaders.DataNode; import org.openide.loaders.DataObject; import org.openide.loaders.DataObjectExistsException; +import org.openide.loaders.DataObjectNotFoundException; import org.openide.loaders.MultiDataObject; import org.openide.nodes.Children; import org.openide.nodes.CookieSet; import org.openide.nodes.Node; +import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.WeakListeners; import static java.util.logging.Level.FINER; @@ -79,12 +78,12 @@ /** Generated Serialized Version UID. */ static final long serialVersionUID = 4795737295255253334L; - + static final Logger LOG = Logger.getLogger(PropertiesDataObject.class.getName()); /** Structural view of the dataobject */ private transient BundleStructure bundleStructure; - + /** Open support for this data object. Provides editable table view on bundle. */ private transient PropertiesOpen openSupport; @@ -94,7 +93,7 @@ // Hack due having lock on secondaries, can't override handleCopy, handleMove at all. /** Suffix used by copying/moving dataObject. */ private transient String pasteSuffix; - + /** */ private Lookup lookup; @@ -110,7 +109,7 @@ * for the specified file */ public PropertiesDataObject(final FileObject primaryFile, - final PropertiesDataLoader loader) + final PropertiesDataLoader loader) throws DataObjectExistsException { super(primaryFile, loader); // use editor support @@ -122,12 +121,12 @@ PropertiesEncoding getEncoding() { return ((PropertiesDataLoader) getLoader()).getEncoding(); } - + @Override public Lookup getLookup() { return getCookieSet().getLookup(); } - + /** Initializes the object. Used by construction and deserialized. */ private void initialize() { bundleStructure = null; @@ -154,7 +153,7 @@ CookieSet getCookieSet0() { return getCookieSet(); } - + /** Copies primary and secondary files to new folder. * Overrides superclass method. * @param df the new folder @@ -175,7 +174,7 @@ pasteSuffix = null; } } - + /** Moves primary and secondary files to a new folder. * Overrides superclass method. * @param df the new folder @@ -197,13 +196,13 @@ try { pasteSuffix = createPasteSuffix(df); - + return super.handleMove(df); } finally { pasteSuffix = null; } } - + /** Gets suffix used by entries by copying/moving. */ String getPasteSuffix() { return pasteSuffix; @@ -215,7 +214,7 @@ void removeSecondaryEntry2(Entry fe) { if (LOG.isLoggable(FINER)) { LOG.finer("removeSecondaryEntry2(Entry " //NOI18N - + FileUtil.getFileDisplayName(fe.getFile()) + ')'); + + FileUtil.getFileDisplayName(fe.getFile()) + ')'); } removeSecondaryEntry (fe); } @@ -226,26 +225,26 @@ String basicName = getPrimaryFile().getName(); DataObject[] children = folder.getChildren(); - - + + // Repeat until there is not such file name. for(int i = 0; ; i++) { String newName; - + if (i == 0) { newName = basicName; } else { newName = basicName + i; } boolean exist = false; - + for(int j = 0; j < children.length; j++) { if(children[j] instanceof PropertiesDataObject && newName.equals(children[j].getName())) { exist = true; break; } } - + if(!exist) { if (i == 0) { return ""; // NOI18N @@ -258,13 +257,24 @@ /** Returns open support. It's used by all subentries as open support too. */ public PropertiesOpen getOpenSupport() { - synchronized(OPEN_SUPPORT_LOCK) { - if(openSupport == null) { - openSupport = new PropertiesOpen(this); - } - - return openSupport; - } +// synchronized (OPEN_SUPPORT_LOCK) { + return ((MultiBundleStructure)getBundleStructure()).getOpenSupport(); +// PropertiesDataObject dataObject = this; +// try { +// dataObject = Util.findPrimaryDataObject(this); +// } catch (DataObjectNotFoundException ex) { +// Exceptions.printStackTrace(ex); +// } +// if (this == dataObject) { +// if (openSupport == null) { +// openSupport = new PropertiesOpen(this); +// } +// +// return openSupport; +// } else { +// return dataObject.getOpenSupport(); +// } +// } } /** Updates modification status of this dataobject from its entries. */ @@ -294,7 +304,7 @@ * * @return the node representation for this data object * @see DataNode - */ + */ @Override protected Node createNodeDelegate () { return new PropertiesDataNode(this); @@ -304,15 +314,28 @@ return new PropertiesChildren(); } + //TODO XXX Now it is always false boolean isMultiLocale() { return secondaryEntries().size() > 0; } /** Returns a structural view of this data object */ public BundleStructure getBundleStructure() { - if (bundleStructure == null) - bundleStructure = new BundleStructure(this); - return bundleStructure; + PropertiesDataObject dataObject = null; + try { + dataObject = Util.findPrimaryDataObject(this); + } catch (DataObjectNotFoundException ex) { + Exceptions.printStackTrace(ex); + } + if (this == dataObject) { + if (bundleStructure == null) { + bundleStructure = new MultiBundleStructure(this); + ((MultiBundleStructure)bundleStructure).updateEntries(); + } + return bundleStructure; + } else { + return dataObject.getBundleStructure(); + } } /** Comparator used for ordering secondary files, works over file names */ @@ -326,14 +349,14 @@ LOG.finer("fireNameChange()"); //NOI18N firePropertyChange(PROP_NAME, null, null); } - + /** Deserialization. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); initialize(); } - + /** Children of this PropertiesDataObject. */ private class PropertiesChildren extends Children.Keys { @@ -341,13 +364,13 @@ private PropertyChangeListener propertyListener = null; private PropertyChangeListener weakPropListener = null; - + /** Constructor.*/ PropertiesChildren() { super(); } - + /** Sets all keys in the correct order */ protected void mySetKeys() { TreeSet newKeys = new TreeSet(new Comparator() { @@ -366,7 +389,7 @@ }); newKeys.add(getPrimaryEntry().getFile().getName()); - + for (Entry entry : secondaryEntries()) { newKeys.add(entry.getFile().getName()); } @@ -379,14 +402,14 @@ @Override protected void addNotify () { mySetKeys(); - + // listener if(propertyListener == null) { propertyListener = new PropertyChangeListener () { public void propertyChange(PropertyChangeEvent evt) { if(PROP_FILES.equals(evt.getPropertyName())) { if (isMultiLocale()) { - mySetKeys(); + mySetKeys(); } else { // These children are only used for two or more locales. // If only default locale is left, disconnect the listener. @@ -395,11 +418,11 @@ if (propertyListener != null) { PropertiesDataObject.this.removePropertyChangeListener(weakPropListener); propertyListener = null; + } + } } } - } - } - }; + }; weakPropListener = WeakListeners.propertyChange(propertyListener, PropertiesDataObject.this); PropertiesDataObject.this.addPropertyChangeListener(weakPropListener); } @@ -419,20 +442,20 @@ if (entryName == null) { return null; } - + PropertiesFileEntry entry = (PropertiesFileEntry)getPrimaryEntry(); - + if(entryName.equals(entry.getFile().getName())) { return new Node[] {entry.getNodeDelegate()}; } for(Iterator it = secondaryEntries().iterator();it.hasNext();) { entry = (PropertiesFileEntry)it.next(); - + if (entryName.equals(entry.getFile().getName())) { return new Node[] {entry.getNodeDelegate()}; } } - + return null; } --- a/properties/src/org/netbeans/modules/properties/PropertiesOpen.java +++ a/properties/src/org/netbeans/modules/properties/PropertiesOpen.java @@ -57,8 +57,11 @@ import java.io.ObjectInput; import java.io.ObjectOutput; import java.io.Serializable; +import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashMap; import java.util.Iterator; +import java.util.List; import javax.swing.Action; import javax.swing.event.ChangeListener; import javax.swing.JPanel; @@ -91,7 +94,6 @@ import org.openide.util.actions.SystemAction; import org.openide.windows.CloneableOpenSupport; import org.openide.windows.CloneableTopComponent; -import org.openide.util.Utilities; import org.openide.DialogDescriptor; import org.openide.filesystems.FileUtil; @@ -105,8 +107,13 @@ implements OpenCookie, CloseCookie { /** Main properties dataobject */ + @Deprecated PropertiesDataObject propDataObject; - + + + private List dataObjectList; + + private BundleStructure bundleStructure; /** Listener for modificationc on dataobject, adding and removing save cookie */ PropertyChangeListener modifL; @@ -118,16 +125,46 @@ /** Constructor */ + @Deprecated public PropertiesOpen(PropertiesDataObject propDataObject) { super(new Environment(propDataObject)); this.propDataObject = propDataObject; - - this.propDataObject.addPropertyChangeListener(WeakListeners.propertyChange(modifL = + + //PENDING Add Listeners for all DataObject from this OpenSupport + this.propDataObject.addPropertyChangeListener(WeakListeners.propertyChange(modifL = new ModifiedListener(), this.propDataObject)); } + public PropertiesOpen(BundleStructure structure) { + super(new Environment(structure)); + this.bundleStructure = structure; + addModifiedListeners(); + } + + private void addModifiedListeners() { + BundleStructure structure = bundleStructure; + dataObjectList = new ArrayList(); + PropertiesDataObject dataObject; + modifL = new ModifiedListener(); + for (int i=0;i map = new HashMap(); + for (int i=0;i weakListeners; + /** Generated serial version UID. */ static final long serialVersionUID =2836248291419024296L; @@ -649,12 +784,25 @@ /** Constructor. * @param propDataObject data object we belong to */ + @Deprecated public PropertiesCloneableTopComponent (PropertiesDataObject propDataObject) { this.propDataObject = propDataObject; initialize(); } + private MultiBundleStructure bundleStructure; + + public PropertiesCloneableTopComponent(BundleStructure structure) { + this.bundleStructure = (MultiBundleStructure) structure; + propDataObject = (PropertiesDataObject) bundleStructure.getNthEntry(0).getDataObject(); + dataObjecs = new PropertiesDataObject[bundleStructure.getEntryCount()]; + for (int i=0; i(); + initialize(); + } /** */ @Override @@ -681,14 +829,32 @@ private void initialize() { initComponents(); setupActions(); - setActivatedNodes(new Node[] {propDataObject.getNodeDelegate()}); + //TODO XXX PENDING Probable need to add activated Nodes + BundleStructure structure = bundleStructure; + PropertiesDataObject dataObject; + Node[] node = new Node[structure.getEntryCount()]; dataObjectListener = new NameUpdater(); - propDataObject.addPropertyChangeListener( - WeakListeners.propertyChange(dataObjectListener, - propDataObject)); + + for( int i=0; i")) { //NOI18N @@ -894,22 +1068,30 @@ /** Gets string for tooltip. */ private String messageToolTip() { - FileObject fo = propDataObject.getPrimaryFile(); + FileObject fo = bundleStructure.getNthEntry(0).getFile(); return FileUtil.getFileDisplayName(fo); } - /** + /** + * * Overrides superclass method. When closing last view, also close the document. * @return {@code true} if close succeeded */ @Override protected boolean closeLast () { - if (!propDataObject.getOpenSupport().canClose ()) { + if (!bundleStructure.getOpenSupport().canClose ()) { // if we cannot close the last window return false; } - propDataObject.getOpenSupport().closeDocuments(); - + bundleStructure.getOpenSupport().closeDocuments(); + PropertyChangeListener l; + for (PropertiesDataObject dataObject:dataObjecs) { + l = weakListeners.get(dataObject); + if (l!=null) { + dataObject.removePropertyChangeListener(l); + weakListeners.remove(l); + } + } return true; } @@ -920,7 +1102,7 @@ */ @Override protected CloneableTopComponent createClonedObject () { - return new PropertiesCloneableTopComponent(propDataObject); + return new PropertiesCloneableTopComponent(bundleStructure); } /** Gets {@code Icon}. */ @@ -950,7 +1132,7 @@ */ @Override public UndoRedo getUndoRedo () { - return propDataObject.getOpenSupport().getUndoRedo(); + return bundleStructure.getOpenSupport().getUndoRedo(); } /** Inits the subcomponents. Sets layout for this top component and adds {@code BundleEditPanel} to it. @@ -964,7 +1146,7 @@ c.weightx = 1.0; c.weighty = 1.0; c.gridwidth = GridBagConstraints.REMAINDER; - JPanel panel = new BundleEditPanel(propDataObject, new PropertiesTableModel(propDataObject.getBundleStructure())); + JPanel panel = new BundleEditPanel(bundleStructure, new PropertiesTableModel(bundleStructure)); gridbag.setConstraints(panel, c); add(panel); } @@ -973,7 +1155,7 @@ * is not valid. */ private boolean discard () { - return propDataObject == null; + return bundleStructure == null; } @@ -985,7 +1167,7 @@ @Override public void writeExternal (ObjectOutput out) throws IOException { super.writeExternal(out); - out.writeObject(propDataObject); + out.writeObject(bundleStructure.getNthEntry(0).getDataObject()); } /** @@ -998,7 +1180,12 @@ super.readExternal(in); propDataObject = (PropertiesDataObject)in.readObject(); - + bundleStructure = (MultiBundleStructure) propDataObject.getBundleStructure(); + dataObjecs = new PropertiesDataObject[bundleStructure.getEntryCount()]; + for (int i=0;i(); initialize(); } } // End of nested class PropertiesCloneableTopComponent. @@ -1017,22 +1204,39 @@ // Constructor /** Collects all UndoRedo managers from all editor support of all entries. */ + @Deprecated public CompoundUndoRedoManager(PropertiesDataObject obj) { init(obj); } + public CompoundUndoRedoManager(BundleStructure structure) { + init(structure); + } /** Initialize set of managers. */ + @Deprecated private void init(PropertiesDataObject obj) { - managers.add( ((PropertiesFileEntry)obj.getPrimaryEntry()).getPropertiesEditor().getUndoRedoManager()); - for (Iterator it = obj.secondaryEntries().iterator(); it.hasNext(); ) { - managers.add( ((PropertiesFileEntry)it.next()).getPropertiesEditor().getUndoRedoManager() ); - } + BundleStructure structure = obj.getBundleStructure(); + for(int i=0; i< structure.getEntryCount(); i++) { + managers.add(structure.getNthEntry(i).getPropertiesEditor().getUndoRedoManager()); + } + } + + private void init(BundleStructure structure) { + for(int i=0; i< structure.getEntryCount(); i++) { + managers.add(structure.getNthEntry(i).getPropertiesEditor().getUndoRedoManager()); + } } /** Resets the managers. Used when data object has changed. */ + @Deprecated public synchronized void reset(PropertiesDataObject obj) { managers.clear(); init(obj); + } + + public synchronized void reset(BundleStructure structure) { + managers.clear(); + init(structure); } /** Gets manager which undo edit comes to play.*/ --- a/properties/src/org/netbeans/modules/properties/PropertiesTableModel.java +++ a/properties/src/org/netbeans/modules/properties/PropertiesTableModel.java @@ -284,6 +284,15 @@ fireTableChanged(new TableModelEvent(this, 0, getRowCount() - 1, columnModelIndex)); } + /** + * Get FileEntry according to column in Table View + * XXX Check it out. + * @param column + * @return + */ + PropertiesFileEntry getFileEntry(int column) { + return structure.getNthEntry(column-1); + } /** Overrides superclass method. */ @Override public String toString() { @@ -364,8 +373,8 @@ // Note: Normal way would be use the next commented out rows, which should do in effect // the same thing like reseting the model, but it doesn't, therefore we reset the model directly. - //cancelEditingInTables(getDefaultCancelSelector()); - //fireTableStructureChanged(); + cancelEditingInTables(getDefaultCancelSelector()); + fireTableStructureChanged(); Object[] list = PropertiesTableModel.super.listenerList.getListenerList(); for(int i = 0; i < list.length; i++) { @@ -396,9 +405,11 @@ } else if(changeType == PropertyBundleEvent.CHANGE_FILE) { // File changed. final int index = structure.getEntryIndexByFileName(evt.getEntryName()); + //This mean file deleted if (index == -1) { - if (Boolean.getBoolean("netbeans.debug.exceptions")) // NOI18N - (new Exception("Changed file not found")).printStackTrace(); // NOI18N + fireTableStructureChanged(); +// if (Boolean.getBoolean("netbeans.debug.exceptions")) // NOI18N +// (new Exception("Changed file not found")).printStackTrace(); // NOI18N return; } @@ -409,8 +420,9 @@ return (column == index + 1); } }); - - fireTableColumnChanged(index + 1); +//TODO XXX PENDING +// fireTableColumnChanged(index + 1); + fireTableStructureChanged(); } else if(changeType == PropertyBundleEvent.CHANGE_ITEM) { // one item changed final int index2 = structure.getEntryIndexByFileName(evt.getEntryName()); --- a/properties/src/org/netbeans/modules/properties/Util.java +++ a/properties/src/org/netbeans/modules/properties/Util.java @@ -45,6 +45,8 @@ import java.io.IOException; import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashSet; import java.util.Locale; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; @@ -55,6 +57,7 @@ import org.openide.filesystems.Repository; import org.openide.loaders.DataFolder; import org.openide.loaders.DataObject; +import org.openide.loaders.DataObjectNotFoundException; import org.openide.loaders.FileEntry; import org.openide.loaders.MultiDataObject; @@ -148,14 +151,69 @@ * @see #getVariant */ public static String getLocaleSuffix(MultiDataObject.Entry fe) { - MultiDataObject.Entry pe = fe.getDataObject().getPrimaryEntry(); - if (fe == pe) { - return ""; //NOI18N + FileObject fo = fe.getFile(); + String fName = fo.getName(); + int index = fName.indexOf(PRB_SEPARATOR_CHAR); + FileObject po = fo; + while (index != -1) { + FileObject candidate = fo.getParent().getFileObject( + fName.substring(0, index), fo.getExt()); + if (candidate != null && isValidLocaleSuffix(fName.substring(index))) { + po = candidate; + } + index = fName.indexOf(PRB_SEPARATOR_CHAR, index + 1); } - String myName = fe.getFile().getName(); - String baseName = pe.getFile().getName(); + if (fo == po) { + return ""; + } + String myName = fo.getName(); + String baseName = po.getName(); assert myName.startsWith(baseName); return myName.substring(baseName.length()); +// MultiDataObject.Entry pe = fe.getDataObject().getPrimaryEntry(); +// if (fe == pe) { +// return ""; //NOI18N +// } +// String myName = fe.getFile().getName(); +// String baseName = pe.getFile().getName(); +// assert myName.startsWith(baseName); +// return myName.substring(baseName.length()); + } + + private static boolean isValidLocaleSuffix(String s) { + // first char is _ + int n = s.length(); + String s1; + // check first suffix - language (two chars) + if (n == 3 || (n > 3 && s.charAt(3) == PropertiesDataLoader.PRB_SEPARATOR_CHAR)) { + s1 = s.substring(1, 3).toLowerCase(); + // language must be followed by a valid country suffix or no suffix + } else { + return false; + } + // check second suffix - country (two chars) + String s2; + if (n == 3) { + s2 = null; + } else if (n == 6 || (n > 6 && s.charAt(6) == PropertiesDataLoader.PRB_SEPARATOR_CHAR)) { + s2 = s.substring(4, 6).toUpperCase(); + // country may be followed by whatever additional suffix + } else { + return false; + } + + HashSet knownLanguages = new HashSet(Arrays.asList(Locale.getISOLanguages())); + if (!knownLanguages.contains(s1)) { + return false; + } + + if (s2 != null) { + HashSet knownCountries = new HashSet(Arrays.asList(Locale.getISOCountries())); + if (!knownCountries.contains(s2)) { + return false; + } + } + return true; } /** @@ -392,6 +450,12 @@ template.createFromTemplate(DataFolder.findFolder(folder), fileName); } + /** + * Create new DataObject with requested locale and notify that new locale was added + * @param propertiesDataObject DataObject to add locale + * @param locale + * @param copyInitialContent + */ public static void createLocaleFile(PropertiesDataObject propertiesDataObject, String locale, boolean copyInitialContent) @@ -404,8 +468,10 @@ } if(propertiesDataObject != null) { - FileObject file = propertiesDataObject.getPrimaryFile(); - final String newName = file.getName() + PropertiesDataLoader.PRB_SEPARATOR_CHAR + locale; +// FileObject file = propertiesDataObject.getPrimaryFile(); + FileObject file = propertiesDataObject.getBundleStructure().getNthEntry(0).getFile(); + //Default locale may be deleted + final String newName = getBaseName(file.getName()) + PropertiesDataLoader.PRB_SEPARATOR_CHAR + locale; final FileObject folder = file.getParent(); // final PropertiesEditorSupport editor = (PropertiesEditorSupport)propertiesDataObject.getCookie(PropertiesEditorSupport.class); java.util.Iterator it = propertiesDataObject.secondaryEntries().iterator(); @@ -430,6 +496,15 @@ templateFile.copy(folder, newName, PropertiesDataLoader.PROPERTIES_EXTENSION); } }); + //update entries in BundleStructure + propertiesDataObject.getBundleStructure().updateEntries(); + //find just created DataObject + PropertiesDataObject dataObject = (PropertiesDataObject) DataObject.find(folder.getFileObject(newName, PropertiesDataLoader.PROPERTIES_EXTENSION)); + //Add it to OpenSupport + propertiesDataObject.getOpenSupport().addDataObject(dataObject); + //Notify BundleStructure that one file changed + propertiesDataObject.getBundleStructure().notifyOneFileChanged(folder.getFileObject(newName, PropertiesDataLoader.PROPERTIES_EXTENSION)); +// propertiesDataObject.getBundleStructure().notifyFileAdded(folder.getFileObject(newName, PropertiesDataLoader.PROPERTIES_EXTENSION)); } } else { // Create an empty file - creating from template via DataObject @@ -445,4 +520,61 @@ notifyError(locale); } } + + /** + * + * @param obj DataObject + * @return DataObject which represent default locale + * In case when default locale is absent it will return first found locale as primary, + * which has the same base name + * @throws org.openide.loaders.DataObjectNotFoundException + */ + static PropertiesDataObject findPrimaryDataObject(PropertiesDataObject obj) throws DataObjectNotFoundException { + FileObject primary = obj.getPrimaryFile(); + assert primary != null : "Object " + obj + " cannot have null primary file"; // NOI18N + String fName; + fName = primary.getName(); + String baseName = getBaseName(fName); + FileObject parent = primary.getParent(); + int index = fName.indexOf(PropertiesDataLoader.PRB_SEPARATOR_CHAR); + while (index != -1) { + FileObject candidate = parent.getFileObject( + fName.substring(0, index), primary.getExt()); + if (candidate != null && isValidLocaleSuffix(fName.substring(index))) { + return (PropertiesDataObject) DataObject.find(candidate); + } else if (candidate == null){ + for (FileObject file : parent.getChildren()) { + if (file.getName().indexOf(baseName) != -1) { + if (isValidLocaleSuffix(file.getName().substring(index))) { + return (PropertiesDataObject) DataObject.find(file); + } + } + } + } + index = fName.indexOf(PropertiesDataLoader.PRB_SEPARATOR_CHAR, index + 1); + } + return obj; + } + +// static BundleStructure findBundleStructure(PropertiesDataObject dataObject) { +// +// } + + /** + * @param name file name + * @return Base name for this locale + */ + static String getBaseName(String name) { + String baseName = null; + int index = name.indexOf(PropertiesDataLoader.PRB_SEPARATOR_CHAR); + while (index != -1) { + baseName = name.substring(0, index); + if (baseName != null && isValidLocaleSuffix(name.substring(index))) { + return baseName; + } + index = name.indexOf(PropertiesDataLoader.PRB_SEPARATOR_CHAR, index + 1); + } + return name; + } + }