From 49801f11a7cda2d294ba6139fd29bedb0198de8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Sat, 24 Oct 2026 18:34:53 +0200 Subject: [PATCH 1/5] Fix/reenabled testes for ide/properties --- .github/workflows/main.yml | 3 + .../modules/properties/ElementTest.java | 2 +- .../properties/PropertiesEncodingTest.java | 73 ++++++++++--------- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5e4f4debfbd6..a9b501329ba6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -766,6 +766,9 @@ jobs: - name: ide/projectui run: ant $OPTS -f ide/projectui test + - name: ide/properties + run: ant $OPTS -f ide/properties test + - name: ide/refactoring.api run: ant $OPTS -f ide/refactoring.api test diff --git a/ide/properties/test/unit/src/org/netbeans/modules/properties/ElementTest.java b/ide/properties/test/unit/src/org/netbeans/modules/properties/ElementTest.java index ba0fb37aa914..2967706fc8f6 100644 --- a/ide/properties/test/unit/src/org/netbeans/modules/properties/ElementTest.java +++ b/ide/properties/test/unit/src/org/netbeans/modules/properties/ElementTest.java @@ -105,7 +105,7 @@ public void testEscapeSpecialCharsInKey() throws Exception { checkTranslation("\u0005", "\\u0005"); checkTranslation("\u0006", "\\u0006"); checkTranslation("\u0007", "\\u0007"); - checkTranslation("\b", "\\b"); + checkTranslation("\b", "\\u0008"); checkTranslation("\t", "\\t"); checkTranslation("\n", "\\n"); checkTranslation("\u000b", "\\u000b"); diff --git a/ide/properties/test/unit/src/org/netbeans/modules/properties/PropertiesEncodingTest.java b/ide/properties/test/unit/src/org/netbeans/modules/properties/PropertiesEncodingTest.java index 363771c3ed20..5cc9700bac97 100644 --- a/ide/properties/test/unit/src/org/netbeans/modules/properties/PropertiesEncodingTest.java +++ b/ide/properties/test/unit/src/org/netbeans/modules/properties/PropertiesEncodingTest.java @@ -54,7 +54,7 @@ public void testEncodingOfSingleChar() { compare(encoder.encodeCharForTests((char) 0x01), new byte[] {'\\', 'u', '0', '0', '0', '1'}); compare(encoder.encodeCharForTests((char) 0x0b), - new byte[] {'\\', 'u', '0', '0', '0', 'B'}); + new byte[] {'\\', 'u', '0', '0', '0', 'b'}); compare(encoder.encodeCharForTests((char) 0x19), new byte[] {'\\', 'u', '0', '0', '1', '9'}); compare(encoder.encodeCharForTests((char) 0x20), @@ -76,39 +76,39 @@ public void testEncodingOfSingleChar() { compare(encoder.encodeCharForTests((char) 0x7e), new byte[] {'~'}); compare(encoder.encodeCharForTests((char) 0x7f), - new byte[] {'\\', 'u', '0', '0', '7', 'F'}); + new byte[] {'\\', 'u', '0', '0', '7', 'f'}); compare(encoder.encodeCharForTests((char) 0x81), new byte[] {'\\', 'u', '0', '0', '8', '1'}); compare(encoder.encodeCharForTests((char) 0x89), new byte[] {'\\', 'u', '0', '0', '8', '9'}); compare(encoder.encodeCharForTests((char) 0x8f), - new byte[] {'\\', 'u', '0', '0', '8', 'F'}); + new byte[] {'\\', 'u', '0', '0', '8', 'f'}); compare(encoder.encodeCharForTests((char) 0x90), new byte[] {'\\', 'u', '0', '0', '9', '0'}); compare(encoder.encodeCharForTests((char) 0xa0), - new byte[] {'\\', 'u', '0', '0', 'A', '0'}); + new byte[] {'\\', 'u', '0', '0', 'a', '0'}); compare(encoder.encodeCharForTests((char) 0xad), - new byte[] {'\\', 'u', '0', '0', 'A', 'D'}); + new byte[] {'\\', 'u', '0', '0', 'a', 'd'}); compare(encoder.encodeCharForTests((char) 0xcb), - new byte[] {'\\', 'u', '0', '0', 'C', 'B'}); + new byte[] {'\\', 'u', '0', '0', 'c', 'b'}); compare(encoder.encodeCharForTests((char) 0xd6), - new byte[] {'\\', 'u', '0', '0', 'D', '6'}); + new byte[] {'\\', 'u', '0', '0', 'd', '6'}); compare(encoder.encodeCharForTests((char) 0xec), - new byte[] {'\\', 'u', '0', '0', 'E', 'C'}); + new byte[] {'\\', 'u', '0', '0', 'e', 'c'}); compare(encoder.encodeCharForTests((char) 0xf0), - new byte[] {'\\', 'u', '0', '0', 'F', '0'}); + new byte[] {'\\', 'u', '0', '0', 'f', '0'}); compare(encoder.encodeCharForTests((char) 0xfd), - new byte[] {'\\', 'u', '0', '0', 'F', 'D'}); + new byte[] {'\\', 'u', '0', '0', 'f', 'd'}); compare(encoder.encodeCharForTests((char) 0xfe), - new byte[] {'\\', 'u', '0', '0', 'F', 'E'}); + new byte[] {'\\', 'u', '0', '0', 'f', 'e'}); compare(encoder.encodeCharForTests((char) 0xff), - new byte[] {'\\', 'u', '0', '0', 'F', 'F'}); + new byte[] {'\\', 'u', '0', '0', 'f', 'f'}); compare(encoder.encodeCharForTests((char) 0x0100), new byte[] {'\\', 'u', '0', '1', '0', '0'}); compare(encoder.encodeCharForTests((char) 0x0101), new byte[] {'\\', 'u', '0', '1', '0', '1'}); compare(encoder.encodeCharForTests((char) 0x016e), - new byte[] {'\\', 'u', '0', '1', '6', 'E'}); + new byte[] {'\\', 'u', '0', '1', '6', 'e'}); compare(encoder.encodeCharForTests((char) 0x0777), new byte[] {'\\', 'u', '0', '7', '7', '7'}); compare(encoder.encodeCharForTests((char) 0x0778), @@ -116,19 +116,19 @@ public void testEncodingOfSingleChar() { compare(encoder.encodeCharForTests((char) 0x0877), new byte[] {'\\', 'u', '0', '8', '7', '7'}); compare(encoder.encodeCharForTests((char) 0x0a46), - new byte[] {'\\', 'u', '0', 'A', '4', '6'}); + new byte[] {'\\', 'u', '0', 'a', '4', '6'}); compare(encoder.encodeCharForTests((char) 0x774d), - new byte[] {'\\', 'u', '7', '7', '4', 'D'}); + new byte[] {'\\', 'u', '7', '7', '4', 'd'}); compare(encoder.encodeCharForTests((char) 0x8000), new byte[] {'\\', 'u', '8', '0', '0', '0'}); compare(encoder.encodeCharForTests((char) 0x8800), new byte[] {'\\', 'u', '8', '8', '0', '0'}); compare(encoder.encodeCharForTests((char) 0xabcd), - new byte[] {'\\', 'u', 'A', 'B', 'C', 'D'}); + new byte[] {'\\', 'u', 'a', 'b', 'c', 'd'}); compare(encoder.encodeCharForTests((char) 0xfffe), - new byte[] {'\\', 'u', 'F', 'F', 'F', 'E'}); + new byte[] {'\\', 'u', 'f', 'f', 'f', 'e'}); compare(encoder.encodeCharForTests((char) 0xffff), - new byte[] {'\\', 'u', 'F', 'F', 'F', 'F'}); + new byte[] {'\\', 'u', 'f', 'f', 'f', 'f'}); } public void testEncodingOfString() throws CharacterCodingException { @@ -163,42 +163,43 @@ public void testDecodingOfSingleChar() throws CharacterCodingException { new char[] {'\\', 'n'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'r'}), /* CR */ new char[] {'\\', 'r'}); + // For byte values 0-0x20 see issue #111530 compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', '0'}), - new char[] {(char) 0x00}); + new char[] {'\\', 'u', '0', '0', '0', '0'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', '1'}), - new char[] {(char) 0x01}); + new char[] {'\\', 'u', '0', '0', '0', '1'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', '2'}), - new char[] {(char) 0x02}); + new char[] {'\\', 'u', '0', '0', '0', '2'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', '3'}), - new char[] {(char) 0x03}); + new char[] {'\\', 'u', '0', '0', '0', '3'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', '4'}), - new char[] {(char) 0x04}); + new char[] {'\\', 'u', '0', '0', '0', '4'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', '5'}), - new char[] {(char) 0x05}); + new char[] {'\\', 'u', '0', '0', '0', '5'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', '6'}), - new char[] {(char) 0x06}); + new char[] {'\\', 'u', '0', '0', '0', '6'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', '7'}), - new char[] {(char) 0x07}); + new char[] {'\\', 'u', '0', '0', '0', '7'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', '8'}), - new char[] {(char) 0x08}); + new char[] {'\\', 'u', '0', '0', '0', '8'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', '9'}), - new char[] {'\\', 'u', '0', '0', '0', '9'}); //see issue #111530 + new char[] {'\\', 'u', '0', '0', '0', '9'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', 'a'}), - new char[] {'\n'}); + new char[] {'\\', 'u', '0', '0', '0', 'a'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', 'b'}), - new char[] {(char) 0x0b}); + new char[] {'\\', 'u', '0', '0', '0', 'b'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', 'c'}), - new char[] {'\\', 'u', '0', '0', '0', 'c'}); //see issue #111530 + new char[] {'\\', 'u', '0', '0', '0', 'c'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', 'd'}), - new char[] {'\r'}); + new char[] {'\\', 'u', '0', '0', '0', 'd'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', 'e'}), - new char[] {(char) 0x0e}); + new char[] {'\\', 'u', '0', '0', '0', 'e'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '0', 'f'}), - new char[] {(char) 0x0f}); + new char[] {'\\', 'u', '0', '0', '0', 'f'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '1', '9'}), - new char[] {(char) 0x19}); + new char[] {'\\', 'u', '0', '0', '1', '9'}); compare(decoder.decodeBytesForTests(new byte[] {'\\', 'u', '0', '0', '2', '0'}), - new char[] {'\\', 'u', '0', '0', '2', '0'}); //see issue #111530 + new char[] {'\\', 'u', '0', '0', '2', '0'}); compare(decoder.decodeBytesForTests(new byte[] {' '}), new char[] {(char) 0x20}); compare(decoder.decodeBytesForTests(new byte[] {'!'}), From cd2f5162b6d0d343da14a3e7667568c2a788df25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Wed, 21 Oct 2026 19:24:06 +0200 Subject: [PATCH 2/5] General cleanup for PropertiesEditorSupport/PropertiesEncoding --- .../properties/PropertiesEditorSupport.java | 523 +++++++++--------- .../properties/PropertiesEncoding.java | 326 +++++------ 2 files changed, 422 insertions(+), 427 deletions(-) diff --git a/ide/properties/src/org/netbeans/modules/properties/PropertiesEditorSupport.java b/ide/properties/src/org/netbeans/modules/properties/PropertiesEditorSupport.java index eccd54f227e2..81a77687c1dd 100644 --- a/ide/properties/src/org/netbeans/modules/properties/PropertiesEditorSupport.java +++ b/ide/properties/src/org/netbeans/modules/properties/PropertiesEditorSupport.java @@ -59,8 +59,6 @@ import java.util.Collections; import java.util.Date; import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; @@ -74,7 +72,6 @@ import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoableEdit; import org.netbeans.api.queries.FileEncodingQuery; -import org.netbeans.modules.properties.PropertiesEncoding.PropCharset; import org.netbeans.modules.properties.PropertiesEncoding.PropCharsetEncoder; import org.openide.ErrorManager; import org.openide.awt.UndoRedo; @@ -109,56 +106,62 @@ import org.openide.util.WeakListeners; import org.openide.windows.CloneableOpenSupport; import org.openide.util.Task; -import org.openide.util.TaskListener; import org.openide.util.lookup.ProxyLookup; import org.openide.windows.CloneableTopComponent; import org.openide.windows.TopComponent; + import static java.util.logging.Level.FINER; -import javax.swing.*; + import org.openide.filesystems.StatusDecorator; -/** +import static java.util.logging.Level.FINE; + +/** * Support for viewing .properties files (EditCookie) by opening them in a text editor. * - * @author Petr Jiricka, Peter Zavadsky + * @author Petr Jiricka, Peter Zavadsky * @see org.openide.text.CloneableEditorSupport */ -public class PropertiesEditorSupport extends CloneableEditorSupport +public class PropertiesEditorSupport extends CloneableEditorSupport implements EditCookie, EditorCookie.Observable, PrintCookie, CloseCookie, Serializable, SaveAsCapable { - + /** error manager for CloneableEditorSupport logging and error reporting */ static final Logger LOG = Logger.getLogger("org.netbeans.modules.properties.PropertiesEditorSupport"); // NOI18N - + /** */ private FileStatusListener fsStatusListener; - + /** Visible view of underlying file entry */ transient PropertiesFileEntry myEntry; - + /** Generated serial version UID. */ static final long serialVersionUID =1787354011149868490L; - + private Charset charset; - - /** Constructor. */ + + /** + * Constructor. + * + * @param entry + */ public PropertiesEditorSupport(PropertiesFileEntry entry) { super(new Environment(entry), new ProxyLookup(Lookups.singleton(entry.getDataObject()), entry.getDataObject().getLookup())); this.myEntry = entry; } - + @Override protected Pane createPane() { return (Pane) MultiViews.createCloneableMultiView(PropertiesDataLoader.PROPERTIES_MIME_TYPE, getDataObject()); } - + /** Getter for the environment that was provided in the constructor. * @return the environment */ final CloneableEditorSupport.Env desEnv() { return (CloneableEditorSupport.Env) env; } - - /** + + /** * Overrides superclass method. * Should test whether all data is saved, and if not, prompt the user * to save. Called by my topcomponent when it wants to close its last topcomponent, but the table editor may still be open @@ -177,7 +180,7 @@ protected boolean canClose () { return super.canClose(); } } - + /** Getter of the data object that this support is associated with. * @return data object passed in constructor */ @@ -189,34 +192,32 @@ private boolean isEnvReadOnly() { CloneableEditorSupport.Env myEnv = desEnv(); return myEnv instanceof Environment && !((Environment) myEnv).getFileImpl().canWrite(); } - + /** * */ - final class FsStatusListener implements FileStatusListener, Runnable { - - /** - */ + private final class FsStatusListener implements FileStatusListener, Runnable { + + @Override public void annotationChanged(FileStatusEvent ev) { if (ev.isNameChange() && ev.hasChanged(myEntry.getFile())) { Mutex.EVENT.writeAccess(this); } } - - /** - */ + + @Override public void run() { updateEditorDisplayNames(); } } - + /** */ private void attachStatusListener() { if (fsStatusListener != null) { return; //already attached } - + FileSystem fs; try { fs = myEntry.getFile().getFileSystem(); @@ -224,21 +225,21 @@ private void attachStatusListener() { ErrorManager.getDefault().notify(ErrorManager.ERROR, ex); return; } - + fsStatusListener = new FsStatusListener(); fs.addFileStatusListener( FileUtil.weakFileStatusListener(fsStatusListener, fs)); } - + /** */ private void updateEditorDisplayNames() { assert EventQueue.isDispatchThread(); - + final String title = messageName(); final String htmlTitle = messageHtmlName(); final String toolTip = messageToolTip(); - Enumeration en = allEditors.getComponents(); + Enumeration en = allEditors.getComponents(); while (en.hasMoreElements()) { TopComponent tc = (TopComponent) en.nextElement(); tc.setDisplayName(title); @@ -246,7 +247,7 @@ private void updateEditorDisplayNames() { tc.setToolTipText(toolTip); } } - + /** */ @Override @@ -255,7 +256,7 @@ protected void initializeCloneableEditor(CloneableEditor editor) { } /** - * Overrides superclass method. + * Overrides superclass method. * Let's the super method create the document and also annotates it * with Title and StreamDescription properities. * @param kit kit to user to create the document @@ -264,31 +265,31 @@ protected void initializeCloneableEditor(CloneableEditor editor) { @Override protected StyledDocument createStyledDocument(EditorKit kit) { StyledDocument document = super.createStyledDocument(kit); - + // Set additional proerties to document. // Set document name property. Used in CloneableEditorSupport. document.putProperty(Document.TitleProperty, myEntry.getFile().toString()); - + // Set dataobject to stream desc property. document.putProperty(Document.StreamDescriptionProperty, myEntry.getDataObject()); - + // hook the document to listen for any changes to update changes by // reparsing the document document.addDocumentListener(new javax.swing.event.DocumentListener() { - public void insertUpdate(javax.swing.event.DocumentEvent e) { changed();} - public void changedUpdate(javax.swing.event.DocumentEvent e) { changed();} - public void removeUpdate(javax.swing.event.DocumentEvent e) { changed();} + @Override public void insertUpdate(javax.swing.event.DocumentEvent e) { changed();} + @Override public void changedUpdate(javax.swing.event.DocumentEvent e) { changed();} + @Override public void removeUpdate(javax.swing.event.DocumentEvent e) { changed();} private void changed() { - myEntry.getHandler().autoParse(); + myEntry.getHandler().autoParse(); } }); - + return document; } /** * Reads the file from the stream, filter the guarded section - * comments, and mark the sections in the editor. Overrides superclass method. + * comments, and mark the sections in the editor. Overrides superclass method. * @param document the document to read into * @param inputStream the open stream to read from * @param editorKit the associated editor kit @@ -299,16 +300,12 @@ private void changed() { @Override protected void loadFromStreamToKit(StyledDocument document, InputStream inputStream, EditorKit editorKit) throws IOException, BadLocationException { final Charset c = getCharset(); - final Reader reader = new BufferedReader(new InputStreamReader(inputStream, c)); - - try { + try (Reader reader = new BufferedReader(new InputStreamReader(inputStream, c))) { editorKit.read(reader, document, 0); - } finally { - reader.close(); } } - /** + /** * Adds new lines according actual value of newLineType variable. * Overrides superclass method. * @param document the document to write from @@ -336,7 +333,7 @@ protected void saveFromKitToStream(StyledDocument document, EditorKit editorKit, writer.close(); } } - + private Charset getCharset() { if (charset == null) { charset = FileEncodingQuery.getEncoding(myEntry.getDataObject().getPrimaryFile()); @@ -348,8 +345,8 @@ void resetCharset() { charset = null; } - /** - * Adds a save cookie if the document has been marked modified. Overrides superclass method. + /** + * Adds a save cookie if the document has been marked modified. Overrides superclass method. * @return true if the environment accepted being marked as modified * or false if it refused it and the document should still be unmodified */ @@ -367,17 +364,13 @@ protected boolean notifyModified () { ((Environment)env).addSaveCookie(); - return true; + return true; } - + @Override protected Task reloadDocument(){ Task tsk = super.reloadDocument(); - tsk.addTaskListener(new TaskListener(){ - public void taskFinished(Task task){ - myEntry.getHandler().autoParse(); - } - }); + tsk.addTaskListener(task -> myEntry.getHandler().autoParse()); return tsk; } @@ -385,10 +378,10 @@ public void taskFinished(Task task){ @Override protected void notifyUnmodified () { super.notifyUnmodified(); - + ((Environment)env).removeSaveCookie(); } - + /** */ @Override @@ -396,7 +389,7 @@ public void open() { super.open(); attachStatusListener(); } - + /** Overrides superclass method. Adds checking for opened Table panel. */ @Override protected void notifyClosed() { @@ -413,11 +406,12 @@ protected void notifyClosed() { } } - /** - * Overrides superclass abstract method. + /** + * Overrides superclass abstract method. * Message to display when an object is being opened. * @return the message or null if nothing should be displayed */ + @Override protected String messageOpening() { return NbBundle.getMessage( PropertiesEditorSupport.class, @@ -425,12 +419,13 @@ protected String messageOpening() { getFileLabel() ); } - + /** - * Overrides superclass abstract method. + * Overrides superclass abstract method. * Message to display when an object has been opened. * @return the message or null if nothing should be displayed */ + @Override protected String messageOpened() { return NbBundle.getMessage( PropertiesEditorSupport.class, @@ -438,20 +433,21 @@ protected String messageOpened() { getFileLabel() ); } - + private String getFileLabel() { PropertiesDataObject propDO = (PropertiesDataObject) myEntry.getDataObject(); return propDO.getPrimaryFile().getNameExt(); } - - /** - * Overrides superclass abstract method. + + /** + * Overrides superclass abstract method. * Constructs message that should be used to name the editor component. * @return name of the editor */ + @Override protected String messageName () { if (!myEntry.getDataObject().isValid()) { - return ""; //NOI18N + return ""; //NOI18N } return DataEditorSupport.annotateName(getFileLabel(), false, isModified(), !myEntry.getFile().canWrite()); } @@ -464,7 +460,7 @@ protected String messageHtmlName () { } String rawName = getFileLabel(); - + String annotatedName = null; final FileObject entry = myEntry.getFile(); try { @@ -486,16 +482,17 @@ protected String messageHtmlName () { } catch (FileStateInvalidException ex) { //do nothing and fall through } - + String name = (annotatedName != null) ? annotatedName : /*XXX escape HTML content*/rawName; return DataEditorSupport.annotateName(name, true, isModified(), !myEntry.getFile().canWrite()); } - - /** + + /** * Overrides superclass abstract method. * Is modified and is being closed. * @return text to show to the user */ + @Override protected String messageSave () { return NbBundle.getMessage ( PropertiesEditorSupport.class, @@ -503,42 +500,43 @@ protected String messageSave () { getFileLabel() ); } - - /** + + /** * Overrides superclass abstract method. * Text to use as tooltip for component. * @return text to show to the user */ + @Override protected String messageToolTip () { // copied from DataEditorSupport, more or less FileObject fo = myEntry.getFile(); return DataEditorSupport.toolTip(fo, isModified(), !myEntry.getFile().canWrite()); } - - /** Overrides superclass method. Gets UndoRedo manager which maps + + /** Overrides superclass method. Gets UndoRedo manager which maps * UndoalbleEdit's to StampFlag's. */ @Override protected UndoRedo.Manager createUndoRedoManager () { return new UndoRedoStampFlagManager(); } - - /** - * Helper method. Hack on superclass getUndoRedo() method, to widen its protected modifier. - * Needs to be accessed from outside this class (in PropertiesOpen). - * @see PropertiesOpen + + /** + * Helper method. Hack on superclass getUndoRedo() method, to widen its protected modifier. + * Needs to be accessed from outside this class (in PropertiesOpen). + * @see PropertiesOpen */ UndoRedo.Manager getUndoRedoManager() { return super.getUndoRedo(); } - - /** + + /** * Helper method. Used only by PropertiesOpen support when closing last Table component. - * Note: It's quite ugly by-pass of notifyClosed() method. Should be revised. + * Note: It's quite ugly by-pass of notifyClosed() method. Should be revised. */ void forceNotifyClosed() { super.notifyClosed(); } - + /** Helper method. Saves this entry. */ private void saveThisEntry() throws IOException { FileSystem.AtomicAction aa = new SaveImpl(this); @@ -555,7 +553,7 @@ private void saveThisEntry() throws IOException { final void superSaveDoc() throws IOException { super.saveDocument(); } - + /** * Save the document under a new file name and/or extension. * @param folder New folder to save the DataObject to. @@ -563,6 +561,7 @@ final void superSaveDoc() throws IOException { * @throws java.io.IOException If the operation failed * @since 6.3 */ + @Override public void saveAs( FileObject folder, String fileName ) throws IOException { //ask the user for a new file name to save to String newExtension = FileUtil.getExtension( fileName ); @@ -601,7 +600,7 @@ public void saveAs( FileObject folder, String fileName ) throws IOException { } } } - + private String getFileNameNoExtension(String fileName) { int index = fileName.lastIndexOf("."); // NOI18N @@ -612,21 +611,22 @@ private String getFileNameNoExtension(String fileName) { } } - - /** + + /** * Save the document to a new file. - * @param output + * @param output * @exception IOException on I/O error * @since 6.3 */ private void saveDocumentAs( final OutputStream output ) throws IOException { final StyledDocument myDoc = getDocument(); - + // save the document as a reader class SaveAsWriter implements Runnable { private IOException ex; + @Override public void run() { try { OutputStream os = null; @@ -663,7 +663,7 @@ public void after() throws IOException { saveAsWriter.after(); } - /** Helper method. + /** Helper method. * @return whether there is an table view opened */ public synchronized boolean hasOpenedTableComponent() { PropertiesDataObject dataObject = (PropertiesDataObject) myEntry.getDataObject(); @@ -672,47 +672,22 @@ public synchronized boolean hasOpenedTableComponent() { } return dataObject.getOpenSupport().hasOpenedTableComponent(); } - + /** * Helper method. * @return whether there is an open editor component. */ public synchronized boolean hasOpenedEditorComponent() { - Enumeration en = allEditors.getComponents (); + Enumeration en = allEditors.getComponents(); return en.hasMoreElements (); } - /** Class which exist only due comaptibility with version 3.0. */ - private static final class Env extends Environment { - /** Generated Serialized Version UID. */ - static final long serialVersionUID = -9218186467757330339L; - - /** Used for deserialization. */ - private PropertiesFileEntry entry; - - /** */ - public Env(PropertiesFileEntry entry) { - super(entry); - } - - /** Adds passing entry field to superclass. */ - private void readObject(java.io.ObjectInputStream in) - throws IOException, ClassNotFoundException { - in.defaultReadObject(); - - if (this.entry != null) { - super.entry = this.entry; - } - } - } - - /** Nested class. Implementation of ClonableEditorSupport.Env interface. */ private static class Environment implements CloneableEditorSupport.Env, PropertyChangeListener, SaveCookie { - + /** generated Serialized Version UID */ static final long serialVersionUID = 354528097109874355L; - + /** Entry on which is support build. */ private PropertiesFileEntry entry; @@ -723,15 +698,15 @@ private static class Environment implements CloneableEditorSupport.Env, * This file object can be changed by a call to refresh file. */ private transient FileObject fileObject; - + /** Spport for firing of property changes. */ private transient PropertyChangeSupport propSupp; - + /** Support for firing of vetoable changes. */ private transient VetoableChangeSupport vetoSupp; private transient EnvironmentListener envListener; - + /** Constructor. * @param obj this support should be associated with */ @@ -756,23 +731,23 @@ protected final DataObject getDataObject() { return entry.getDataObject(); } /** Implements CloneableEditorSupport.Env inetrface. Adds property listener. */ + @Override public void addPropertyChangeListener(PropertyChangeListener l) { LOG.finer("Environment.addPropertyChangeListener(...)"); //NOI18N prop().addPropertyChangeListener (l); } - + /** Accepts property changes from entry and fires them to own listeners. */ + @Override public void propertyChange(PropertyChangeEvent evt) { - if (LOG.isLoggable(FINER)) { - LOG.finer("Environment.propertyChange(" //NOI18N - + evt.getPropertyName() - + ", " + evt.getOldValue() //NOI18N - + ", " + evt.getNewValue() //NOI18N - + ')'); - } + LOG.finer(() -> "Environment.propertyChange(" //NOI18N + + evt.getPropertyName() + + ", " + evt.getOldValue() //NOI18N + + ", " + evt.getNewValue() //NOI18N + + ')'); // We will handle the object invalidation here. - if(DataObject.PROP_VALID.equals(evt.getPropertyName ())) { + if(DataObject.PROP_VALID.equals(evt.getPropertyName ())) { // do not check it if old value is not true if (Boolean.FALSE.equals(evt.getOldValue())) { return; @@ -781,7 +756,7 @@ public void propertyChange(PropertyChangeEvent evt) { // loosing validity PropertiesEditorSupport support = (PropertiesEditorSupport)findCloneableOpenSupport(); if(support != null) { - + // mark the object as not being modified, so nobody // will ask for save unmarkModified(); @@ -796,20 +771,23 @@ public void propertyChange(PropertyChangeEvent evt) { ); } } - + /** Implements CloneableEditorSupport.Env inetrface. Removes property listener. */ + @Override public void removePropertyChangeListener(PropertyChangeListener l) { LOG.finer("Environment.removePropertyChangeListener(...)"); //NOI18N prop().removePropertyChangeListener (l); } - + /** Implements CloneableEditorSupport.Env inetrface. Adds veto listener. */ + @Override public void addVetoableChangeListener(VetoableChangeListener l) { LOG.finer("Environment.addVetoableChangeListener(...)"); //NOI18N veto().addVetoableChangeListener (l); } - + /** Implements CloneableEditorSupport.Env inetrface. Removes veto listener. */ + @Override public void removeVetoableChangeListener(VetoableChangeListener l) { LOG.finer("Environment.removeVetoableChangeListener(...)"); //NOI18N veto().removeVetoableChangeListener (l); @@ -821,6 +799,7 @@ public void removeVetoableChangeListener(VetoableChangeListener l) { * state and the CloneableOpenSupport cannot be found for associated * entry object */ + @Override public CloneableOpenSupport findCloneableOpenSupport() { return (PropertiesEditorSupport)entry.getCookieSet().getCookie(EditCookie.class); } @@ -832,15 +811,17 @@ public CloneableOpenSupport findCloneableOpenSupport() { * referenced to does not exist anymore. * @return true or false depending on its state */ + @Override public boolean isValid() { return entry.getDataObject().isValid(); } - + /** * Implements CloneableEditorSupport.Env interface. * Test whether the object is modified or not. * @return true if the object is modified */ + @Override public boolean isModified() { return entry.isModified(); } @@ -853,26 +834,28 @@ public boolean isModified() { * (for example when the file is readonly), when such exception * is the support should discard all previous changes */ + @Override public void markModified() throws java.io.IOException { LOG.finer("Environment.markModified()"); //NOI18N if (fileLock == null || !fileLock.isValid()) { fileLock = entry.takeLock(); } - + entry.setModified(true); } - + /** * Implements CloneableEditorSupport.Env interface. * Reverse method that can be called to make the environment * unmodified. */ + @Override public void unmarkModified() { LOG.finer("Environment.unmarkModified()"); //NOI18N if (fileLock != null && fileLock.isValid()) { fileLock.releaseLock(); } - + entry.setModified(false); } /** @@ -895,13 +878,15 @@ final void updateDocumentProperty () { * Mime type of the document. * @return the mime type to use for the document */ + @Override public String getMimeType() { return getFileImpl().getMIMEType(); } - + /** * Implements CloneableEditorSupport.Env interface. * The time when the data has been modified. */ + @Override public Date getTime() { // #32777 - refresh file object and return always the actual time getFileImpl().refresh(); @@ -928,7 +913,7 @@ protected final void changeFile () { // lockAgain = true; // ===== if(fileLock.isValid()) { - LOG.fine("changeFile releaseLock: " + fileLock + " for " + fileObject); // NOI18N + LOG.log(FINE, "changeFile releaseLock: {0} for {1}", new Object[]{fileLock, fileObject}); // NOI18N fileLock.releaseLock (); lockAgain = true; } else { @@ -943,7 +928,7 @@ protected final void changeFile () { boolean wasNull = fileObject == null; fileObject = newFile; - LOG.fine("changeFile: " + newFile + " for " + fileObject); // NOI18N + LOG.log(FINE, "changeFile: {0} for {1}", new Object[]{newFile, fileObject}); // NOI18N if (envListener != null) fileObject.removeFileChangeListener(envListener); envListener = new EnvironmentListener(this); @@ -952,7 +937,7 @@ protected final void changeFile () { if (lockAgain) { // refresh lock try { fileLock = entry.takeLock (); - LOG.fine("changeFile takeLock: " + fileLock + " for " + fileObject); // NOI18N + LOG.log(FINE, "changeFile takeLock: {0} for {1}", new Object[]{fileLock, fileObject}); // NOI18N } catch (IOException e) { Logger.getLogger(PropertiesEditorSupport.class.getName()).log(Level.WARNING, null, e); } @@ -961,28 +946,30 @@ protected final void changeFile () { firePropertyChange("expectedTime", null, getTime()); // NOI18N } } - + /** * Implements CloneableEditorSupport.Env interface. * Obtains the input stream. * @exception IOException if an I/O error occures */ + @Override public InputStream inputStream() throws IOException { LOG.finer("Environment.inputStream()"); //NOI18N return getFileImpl().getInputStream(); } - + /** * Implements CloneableEditorSupport.Env interface. * Obtains the output stream. * @exception IOException if an I/O error occures */ + @Override public OutputStream outputStream() throws IOException { LOG.finer("Environment.outputStream()"); //NOI18N if (fileLock == null || !fileLock.isValid()) { fileLock = entry.takeLock (); } - LOG.fine("outputStream after takeLock: " + fileLock + " for " + fileObject); // NOI18N + LOG.fine(() -> "outputStream after takeLock: " + fileLock + " for " + fileObject); // NOI18N try { return getFileImpl ().getOutputStream (fileLock); } catch (IOException fse) { @@ -991,17 +978,18 @@ public OutputStream outputStream() throws IOException { if (fileLock == null || !fileLock.isValid()) { fileLock = entry.takeLock (); } - LOG.fine("ugly workaround for #40552: " + fileLock + " for " + fileObject); // NOI18N + LOG.fine(() -> "ugly workaround for #40552: " + fileLock + " for " + fileObject); // NOI18N return getFileImpl ().getOutputStream (fileLock); } // return entry.getFile().getOutputStream(fileLock); } /** - * Implements SaveCookie interface. + * Implements SaveCookie interface. * Invoke the save operation. * @throws IOException if the object could not be saved */ + @Override public void save() throws IOException { LOG.finer("Environment.save()"); //NOI18N // Do saving job. Note it gets editor support, not open support. @@ -1016,7 +1004,7 @@ public void save() throws IOException { private void firePropertyChange (String name, Object oldValue, Object newValue) { prop().firePropertyChange (name, oldValue, newValue); } - + /** Fires vetoable change. * @param name the name of property that changed * @param oldValue old value @@ -1025,7 +1013,7 @@ private void firePropertyChange (String name, Object oldValue, Object newValue) private void fireVetoableChange (String name, Object oldValue, Object newValue) throws PropertyVetoException { veto ().fireVetoableChange (name, oldValue, newValue); } - + /** Lazy getter for property change support. */ private PropertyChangeSupport prop() { synchronized (this) { @@ -1033,10 +1021,10 @@ private PropertyChangeSupport prop() { propSupp = new PropertyChangeSupport (this); } } - + return propSupp; } - + /** Lazy getter for vetoable support. */ private VetoableChangeSupport veto() { synchronized (this) { @@ -1046,7 +1034,7 @@ private VetoableChangeSupport veto() { } return vetoSupp; } - + /** Helper method. Adds save cookie to the entry. */ private void addSaveCookie() { LOG.finer("Environment.addSaveCookie(...)"); //NOI18N @@ -1061,26 +1049,24 @@ private void addSaveCookie() { dataObject.getCookieSet0().add(this); } } - + /** Helper method. Removes save cookie from the entry. */ private void removeSaveCookie() { LOG.finer("Environment.removeSaveCookie(...)"); //NOI18N // remove Save cookie from the entry SaveCookie sc = entry.getCookie(SaveCookie.class); - + if (sc != null && sc.equals(this)) { entry.getCookieSet().remove(this); } final SaveCookie cookie = this; - PropertiesRequestProcessor.getInstance().post(new Runnable() { - public void run() { - PropertiesDataObject dataObject = (PropertiesDataObject) getDataObject(); - dataObject.updateModificationStatus(); - dataObject.getCookieSet0().remove(cookie); - } + PropertiesRequestProcessor.getInstance().post(() -> { + PropertiesDataObject dataObject = (PropertiesDataObject) getDataObject(); + dataObject.updateModificationStatus(); + dataObject.getCookieSet0().remove(cookie); }); } - + /** Called from the EnvironmnetListener * @param expected is the change expected * @param time of the change @@ -1101,7 +1087,7 @@ final void fileRenamed () { //#151787: Sync timestamp when svn client changes timestamp externally during rename. firePropertyChange("expectedTime", null, getTime()); // NOI18N } - + /** Called from the EnvironmentListener. * The components are going to be closed anyway and in case of * modified document its asked before if to save the change. */ @@ -1112,25 +1098,29 @@ private void fileRemoved() { } catch(PropertyVetoException pve) { // Ignore it and close anyway. File doesn't exist anymore. } - + firePropertyChange(PROP_VALID, Boolean.TRUE, Boolean.FALSE); } + + PropertiesFileEntry getEntry() { + return entry; + } } // End of nested class Environment. - + /** Weak listener on file object that notifies the Environment object * that a file has been modified. */ private static final class EnvironmentListener extends FileChangeAdapter { - + /** Reference of Environment */ private Reference reference; - + /** @param environment Environment to use */ public EnvironmentListener(Environment environment) { LOG.finer("new EnvironmentListener()"); //NOI18N - reference = new WeakReference(environment); + reference = new WeakReference<>(environment); } @Override @@ -1142,7 +1132,7 @@ public void fileDeleted(FileEvent fe) { } // super.fileDeleted(fe); } - + /** Fired when a file is changed. * @param fe the event describing context where action has taken place */ @@ -1150,10 +1140,10 @@ public void fileDeleted(FileEvent fe) { public void fileChanged(FileEvent evt) { if (LOG.isLoggable(FINER)) { LOG.finer("EnviromentListener.fileChanged(...)"); //NOI18N - LOG.finer(" - original file: " //NOI18N - + FileUtil.getFileDisplayName(evt.getFile())); - LOG.finer(" - current file: " //NOI18N - + FileUtil.getFileDisplayName((FileObject) evt.getSource())); + LOG.log(Level.FINER, " - original file: ", //NOI18N + FileUtil.getFileDisplayName(evt.getFile())); + LOG.log(Level.FINER, " - current file: ", //NOI18N + FileUtil.getFileDisplayName((FileObject) evt.getSource())); } //see #160338 if (evt.firedFrom(SaveImpl.DEFAULT)) { @@ -1172,17 +1162,17 @@ public void fileChanged(FileEvent evt) { // #16403. See DataEditorSupport.EnvListener. if(evt.getFile().isVirtual()) { - environment.entry.getFile().removeFileChangeListener(this); + environment.getEntry().getFile().removeFileChangeListener(this); // File doesn't exist on disk -> simulate env is invalid, // even the fileObject could be valid, see VCS FS. environment.fileRemoved(); - environment.entry.getFile().addFileChangeListener(this); + environment.getEntry().getFile().addFileChangeListener(this); } else { environment.fileChanged(evt.isExpected(), evt.getTime()); } } } - + @Override public void fileRenamed(FileRenameEvent fe) { Environment myEnv = this.reference.get(); @@ -1192,32 +1182,36 @@ public void fileRenamed(FileRenameEvent fe) { } } } // End of nested class EnvironmentListener. - + /** Inner class for opening editor view at a given key. */ public class PropertiesEditAt implements EditCookie { /** Key at which should be pane opened. (Cursor will be at the position of that key). */ private String key; - - + + /** Constructor. */ PropertiesEditAt(String key) { this.key = key; } - - - /** Setter for key. */ + + + /** + * Setter for key. + * @param key + */ public void setKey(String key) { this.key = key; } - + /** Implementation of EditCookie interface. */ + @Override public void edit() { CloneableTopComponent ctc = PropertiesEditorSupport.super.openCloneableTopComponent(); ctc.requestActive(); - + PropertiesEditor editor = ctc.getLookup().lookup(PropertiesEditor.class); - + Element.ItemElem item = myEntry.getHandler().getStructure().getItem(key); if (item != null) { int offset = item.getKeyElem().getBounds().getBegin().getOffset(); @@ -1228,7 +1222,7 @@ public void edit() { } } // End of inner class PropertiesEditAt. - + /** Cloneable top component to hold the editor kit. */ @Messages("CTL_SourceTabCaption=&Source") @MultiViewElement.Registration( @@ -1238,29 +1232,33 @@ public void edit() { preferredID="properties.source", mimeType=PropertiesDataLoader.PROPERTIES_MIME_TYPE, position=1 - ) + ) public static class PropertiesEditor extends CloneableEditor implements MultiViewElement { - + /** Holds the file being edited. */ + @SuppressWarnings("ProtectedField") // API protected transient PropertiesFileEntry entry; - + /** Listener for entry's save cookie changes. */ private transient PropertyChangeListener saveCookieLNode; - + /** Generated serial version UID. */ static final long serialVersionUID =-2702087884943509637L; private MultiViewElementCallback callback; private transient JToolBar bar; - + private transient PropertiesEditorLookup peLookup; private transient Lookup originalLookup; - + /** Constructor for deserialization */ public PropertiesEditor() { super(); } - - /** Creates new editor */ + + /** + * Creates new editor + * @param lookup + */ public PropertiesEditor(Lookup lookup) { super(lookup.lookup(PropertiesEditorSupport.class)); PropertiesEditorSupport support = lookup.lookup(PropertiesEditorSupport.class); @@ -1270,27 +1268,24 @@ public PropertiesEditor(Lookup lookup) { /** Initializes object, used in construction and deserialization. */ private void initialize(PropertiesFileEntry entry) { this.entry = entry; - + Node n = entry.getNodeDelegate (); setActivatedNodes (new Node[] { n }); - + updateName(); - + // entry to the set of listeners - saveCookieLNode = new PropertyChangeListener() { - public void propertyChange(PropertyChangeEvent evt) { - if (Node.PROP_COOKIE.equals(evt.getPropertyName()) || - DataObject.PROP_NAME.equals(evt.getPropertyName())) - { - updateName(); - } + saveCookieLNode = (PropertyChangeEvent evt) -> { + if (Node.PROP_COOKIE.equals(evt.getPropertyName()) + || DataObject.PROP_NAME.equals(evt.getPropertyName())) { + updateName(); } }; this.entry.addPropertyChangeListener(WeakListeners.propertyChange(saveCookieLNode, this.entry)); } /** - * Overrides superclass method. + * Overrides superclass method. * When closing last view, also close the document. * @return true if close succeeded */ @@ -1302,19 +1297,19 @@ protected boolean closeLast () { /** Overrides superclass method. Gets Icon. */ @Override public Image getIcon () { - PropertiesDataObject propDO = (PropertiesDataObject) getDataObject(); + PropertiesDataObject propDO = getDataObject(); return ImageUtilities.loadImage( propDO.isMultiLocale() ? "org/netbeans/modules/properties/propertiesLocale.gif" // NOI18N : "org/netbeans/modules/properties/propertiesObject.png"); // NOI18N } - + /** Overrides superclass method. Gets help context. */ @Override public HelpCtx getHelpCtx() { return new HelpCtx(Util.HELP_ID_EDITLOCALE); } - + /** Getter for pane. */ private JEditorPane getPane() { return pane; @@ -1332,7 +1327,7 @@ public Lookup getLookup() { } return peLookup; } - + @Override public JComponent getVisualRepresentation() { return this; @@ -1347,7 +1342,7 @@ public void componentDeactivated() { public void componentClosed() { super.componentClosed(); } - + @Override public JComponent getToolbarRepresentation() { JToolBar toolBar = bar; @@ -1355,8 +1350,8 @@ public JComponent getToolbarRepresentation() { JEditorPane lPane = getEditorPane(); if (lPane != null) { Document doc = lPane.getDocument(); - if (doc instanceof NbDocument.CustomToolbar) { - toolBar = ((NbDocument.CustomToolbar)doc).createToolbar(lPane); + if (doc instanceof NbDocument.CustomToolbar customToolbar) { + toolBar = customToolbar.createToolbar(lPane); } } if (toolBar == null) { @@ -1375,12 +1370,13 @@ public void setMultiViewCallback(MultiViewElementCallback callback) { } @Messages({ + "# {0} - filename", "MSG_SaveModified=File {0} is modified. Save?" }) @Override public CloseOperationState canCloseElement() { final CloneableEditorSupport sup = getLookup().lookup(CloneableEditorSupport.class); - Enumeration en = getReference().getComponents(); + Enumeration en = getReference().getComponents(); if (en.hasMoreElements()) { en.nextElement(); if (en.hasMoreElements()) { @@ -1388,7 +1384,7 @@ public CloseOperationState canCloseElement() { return CloseOperationState.STATE_OK; } } - + PropertiesDataObject dataObject = getDataObject(); if (dataObject.isModified()) { AbstractAction save = new AbstractAction() { @@ -1403,7 +1399,7 @@ public void actionPerformed(ActionEvent e) { }; save.putValue(Action.LONG_DESCRIPTION, Bundle.MSG_SaveModified(FileUtil.getFileDisplayName(dataObject.getPrimaryFile()))); return MultiViewFactory.createUnsafeCloseState("editor", save, null); - } + } return CloseOperationState.STATE_OK; } @@ -1451,20 +1447,15 @@ public void requestActive() { @Override public void updateName() { super.updateName(); - Mutex.EVENT.writeAccess( - new Runnable() { - @Override - public void run() { - if (callback != null) { - TopComponent tc = callback.getTopComponent(); - tc.setHtmlDisplayName(getHtmlDisplayName()); - tc.setDisplayName(getDisplayName()); - tc.setName(getName()); - tc.setToolTipText(getToolTipText()); - } - } + Mutex.EVENT.writeAccess(() -> { + if (callback != null) { + TopComponent tc = callback.getTopComponent(); + tc.setHtmlDisplayName(getHtmlDisplayName()); + tc.setDisplayName(getDisplayName()); + tc.setName(getName()); + tc.setToolTipText(getToolTipText()); } - ); + }); } @Override @@ -1475,23 +1466,22 @@ public void open() { super.open(); } } - + private PropertiesDataObject getDataObject() { return (PropertiesDataObject) ((PropertiesEditorSupport) cloneableEditorSupport()).getDataObject(); } - + } // End of nested class PropertiesEditor. - + /** Inner class. UndoRedo manager which saves a StampFlag * for each UndoAbleEdit. */ class UndoRedoStampFlagManager extends UndoRedo.Manager { - + /** Hash map of weak reference keys (UndoableEdit's) to their StampFlag's. */ - WeakHashMap stampFlags - = new WeakHashMap(5); - + WeakHashMap stampFlags = new WeakHashMap<>(5); + /** Overrides superclass method. Adds StampFlag to UndoableEdit. */ @Override public synchronized boolean addEdit(UndoableEdit anEdit) { @@ -1500,16 +1490,16 @@ public synchronized boolean addEdit(UndoableEdit anEdit) { PropertiesEditorSupport.this.myEntry.atomicUndoRedoFlag )); return super.addEdit(anEdit); } - + /** Overrides superclass method. Adds StampFlag to UndoableEdit. */ @Override public boolean replaceEdit(UndoableEdit anEdit) { - stampFlags.put(anEdit, new StampFlag(System.currentTimeMillis(), + stampFlags.put(anEdit, new StampFlag(System.currentTimeMillis(), // ((PropertiesDataObject)PropertiesEditorSupport.this.myEntry.getDataObject()).getOpenSupport().atomicUndoRedoFlag )); PropertiesEditorSupport.this.myEntry.atomicUndoRedoFlag )); return super.replaceEdit(anEdit); } - + /** Overrides superclass method. Updates time stamp for the edit. */ @Override public synchronized void undo() throws CannotUndoException { @@ -1520,7 +1510,7 @@ public synchronized void undo() throws CannotUndoException { stampFlags.put(anEdit, new StampFlag(System.currentTimeMillis(), atomicFlag)); } } - + /** Overrides superclass method. Updates time stamp for that edit. */ @Override public synchronized void redo() throws CannotRedoException { @@ -1531,8 +1521,8 @@ public synchronized void redo() throws CannotRedoException { stampFlags.put(anEdit, new StampFlag(System.currentTimeMillis(), atomicFlag)); } } - - /** Method which gets time stamp of next Undoable edit to be undone. + + /** Method which gets time stamp of next Undoable edit to be undone. * @ return time stamp in milliseconds or 0 (if don't exit edit to be undone). */ public long getTimeStampOfEditToBeUndone() { UndoableEdit nextUndo = editToBeUndone(); @@ -1542,7 +1532,7 @@ public long getTimeStampOfEditToBeUndone() { return stampFlags.get(nextUndo).getTimeStamp(); } } - + /** Method which gets time stamp of next Undoable edit to be redone. * @ return time stamp in milliseconds or 0 (if don't exit edit to be redone). */ public long getTimeStampOfEditToBeRedone() { @@ -1553,8 +1543,8 @@ public long getTimeStampOfEditToBeRedone() { return stampFlags.get(nextRedo).getTimeStamp(); } } - - /** Method which gets atomic flag of next Undoable edit to be undone. + + /** Method which gets atomic flag of next Undoable edit to be undone. * @ return atomic flag in milliseconds or 0 (if don't exit edit to be undone). */ public Object getAtomicFlagOfEditToBeUndone() { UndoableEdit nextUndo = editToBeUndone(); @@ -1564,7 +1554,7 @@ public Object getAtomicFlagOfEditToBeUndone() { return (stampFlags.get(nextUndo)).getAtomicFlag(); } } - + /** Method which gets atomic flag of next Undoable edit to be redone. * @ return time stamp in milliseconds or 0 (if don't exit edit to be redone). */ public Object getAtomicFlagOfEditToBeRedone() { @@ -1575,40 +1565,40 @@ public Object getAtomicFlagOfEditToBeRedone() { return (stampFlags.get(nextRedo)).getAtomicFlag(); } } - + } // End of inner class UndoRedoTimeStampManager. - /** Simple nested class for storing time stamp and atomic flag used + /** Simple nested class for storing time stamp and atomic flag used * in UndoRedoStampFlagManager. */ - static class StampFlag { - - /** Time stamp when was an UndoableEdit (to which is this class mapped via + private static class StampFlag { + + /** Time stamp when was an UndoableEdit (to which is this class mapped via * UndoRedoStampFlagManager,) was created, replaced, undone, or redone. */ private long timeStamp; - + /** Atomic flag. If this object is not null it means that an UndoableEdit ( to which - * is this class mapped via UndoRedoStampFlagManager,) was created as part of one + * is this class mapped via UndoRedoStampFlagManager,) was created as part of one * action which could consist from more UndoableEdits in differrent editor supports. * These Undoable edits are marked with this (i.e. same) object. */ - private Object atomicFlag; - + private final Object atomicFlag; + /** Consructor. */ public StampFlag(long timeStamp, Object atomicFlag) { this.timeStamp = timeStamp; - this.atomicFlag = atomicFlag; + this.atomicFlag = atomicFlag; } - + /** Getter for time stamp. */ public long getTimeStamp() { return timeStamp; } - + /** Setter for time stamp. */ public void setTimeStamp(long timeStamp) { this.timeStamp = timeStamp; } - + /** Getter for atomic flag. @ return Returns null if is not linked with more Undoable edits.*/ public Object getAtomicFlag() { @@ -1624,6 +1614,7 @@ public SaveImpl(PropertiesEditorSupport des) { this.des = des; } + @Override public void run() throws IOException { if (des.desEnv().isModified() && des.isEnvReadOnly()) { IOException e = new IOException("File is read-only: " + ((Environment) des.env).getFileImpl()); // NOI18N @@ -1645,7 +1636,7 @@ public boolean equals(Object obj) { } private static final class PropertiesEditorLookup extends ProxyLookup { - private Lookup initialLookup; + private final Lookup initialLookup; public PropertiesEditorLookup(Lookup lookup) { super(lookup); this.initialLookup = lookup; diff --git a/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java b/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java index 5750e51a8076..aea778049fc1 100644 --- a/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java +++ b/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java @@ -54,12 +54,12 @@ public final class PropertiesEncoding extends FileEncodingQueryImplementation { static final String PROP_CHARSET_NAME = "ISO-8859-1"; /* * TO DO: - * + * * DECODER * - leave some characters in the form of escape sequence, e.g. 0x00 - 0x1f * - allow decoding of supplementary characters (?) */ - + @Override public Charset getEncoding(FileObject file) { assert !file.isValid() || file.isData(); @@ -76,18 +76,17 @@ public Charset getEncoding(FileObject file) { return null; } } - - /** - * - */ + + @SuppressWarnings("PackageVisibleInnerClass") // For unittests static final class PropCharset extends Charset implements FileChangeListener { private final Reference fileRef; private URL fileURL; - + + @SuppressWarnings("LeakingThisInConstructor") PropCharset(FileObject file) throws FileStateInvalidException { super(PROP_CHARSET_NAME, null); //NOI18N - fileRef = new WeakReference(file); + fileRef = new WeakReference<>(file); file.addFileChangeListener(this); updateURL(file); } @@ -97,14 +96,17 @@ static final class PropCharset extends Charset implements FileChangeListener { fileRef = null; } + @Override public boolean contains(Charset charset) { return true; } + @Override public CharsetEncoder newEncoder() { return new PropCharsetEncoder(this); } - + + @Override public CharsetDecoder newDecoder() { long fileSize = (fileRef != null) ? getFileSize() : -1; return (fileSize > 0l) ? new PropCharsetDecoder(this, fileSize) @@ -130,10 +132,12 @@ private FileObject getFile() { return fileObj; } + @Override public void fileRenamed(FileRenameEvent fe) { updateURL(fe.getFile()); } + @Override public void fileChanged(FileEvent fe) { updateURL(fe.getFile()); } @@ -142,65 +146,67 @@ private synchronized void updateURL(FileObject file) { fileURL = file.toURL(); } + @Override public void fileDeleted(FileEvent fe) { } + @Override public void fileAttributeChanged(FileAttributeEvent fe) { } + @Override public void fileDataCreated(FileEvent fe) { /* this should be never called on plain files (non-directories) */ // assert false; } + @Override public void fileFolderCreated(FileEvent fe) { /* this should be never called on plain files (non-directories) */ assert false; } } - - /** - * - * @author Marian Petras - */ + + @SuppressWarnings("PackageVisibleInnerClass") // For unittests static final class PropCharsetEncoder extends CharsetEncoder { - - private static final int avgEncodedTokenLen = 3; - private static final int maxEncodedTokenLen = 6; - - private static final int inBufSize = 8192; - private static final int outBufSize = inBufSize * avgEncodedTokenLen; - - private final char[] inBuf = new char[inBufSize]; - private final byte[] outBuf = new byte[outBufSize]; - + + private static final int AVG_ENCODED_TOKEN_LEN = 3; + private static final int MAX_ENCODED_TOKEN_LEN = 6; + + private static final int IN_BUF_SIZE = 8192; + private static final int OUT_BUF_SIZE = IN_BUF_SIZE * AVG_ENCODED_TOKEN_LEN; + + private final char[] inBuf = new char[IN_BUF_SIZE]; + private final byte[] outBuf = new byte[OUT_BUF_SIZE]; + private int inBufPos, outBufPos; - + private boolean emptyIn; private boolean fullOut; private boolean emptyInBuf; - + PropCharsetEncoder(Charset charset) { - super(charset, avgEncodedTokenLen, maxEncodedTokenLen); + super(charset, AVG_ENCODED_TOKEN_LEN, MAX_ENCODED_TOKEN_LEN); } PropCharsetEncoder() { - super(new PropCharset(), avgEncodedTokenLen, maxEncodedTokenLen); + super(new PropCharset(), AVG_ENCODED_TOKEN_LEN, MAX_ENCODED_TOKEN_LEN); } - + { implReset(); } - + @Override protected void implReset() { inBufPos = 0; outBufPos = 0; - + emptyIn = false; fullOut = false; emptyInBuf = true; } + @Override protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) { emptyIn = false; fullOut = false; @@ -229,13 +235,13 @@ protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) { return OVERFLOW; } } - + @Override protected CoderResult implFlush(ByteBuffer out) { return flushOutBuf(out) ? OVERFLOW : UNDERFLOW; } - + /** * Reads and stores as many characters from the input buffer as possible. * If there are no more characters available in the input buffer, @@ -245,44 +251,45 @@ private void readIn(CharBuffer in) { if (emptyIn) { return; } - + int inRemaining = in.remaining(); if (inRemaining == 0) { emptyIn = true; return; } - + int bufRemaining = inBuf.length - inBufPos; if (bufRemaining == 0) { /* no space in inBuf */ return; } - + int length = min(inRemaining, bufRemaining); in.get(inBuf, inBufPos, length); inBufPos += length; emptyInBuf = false; - + if (length == inRemaining) { assert in.remaining() == 0; emptyIn = true; } } - + /** * Encodes as many chars from the internal input buffer as possible. */ + @SuppressWarnings("ValueOfIncrementOrDecrementUsed") private void encodeBuf() { if (emptyInBuf) { return; } - + int encodingInBufPos = 0; while ((encodingInBufPos < inBufPos) - && (outBufPos <= outBufSize - maxEncodedTokenLen)) { + && (outBufPos <= OUT_BUF_SIZE - MAX_ENCODED_TOKEN_LEN)) { encodeChar(inBuf[encodingInBufPos++]); } - + int remainder = inBufPos - encodingInBufPos; if (remainder != 0) { System.arraycopy(inBuf, encodingInBufPos, @@ -292,11 +299,11 @@ private void encodeBuf() { inBufPos = remainder; emptyInBuf = (inBufPos == 0); } - + /** * Writes as many as possible bytes from the {@code outBuf} to the given * {@code ByteBuffer} and removes the written bytes from {@code outBuf}. - * + * * @return {@code true} if the given {@code out} buffer is overflown, * {@code false} otherwise */ @@ -304,21 +311,21 @@ private boolean flushOutBuf(ByteBuffer out) { if (fullOut) { return true; } - + int outRemaining = out.remaining(); if (outRemaining == 0) { fullOut = true; return true; } - + if (outBufPos == 0) { /* nothing to flush */ return false; } - + int length = min(outRemaining, outBufPos); out.put(outBuf, 0, length); - + int remainder = outBufPos - length; if (remainder != 0) { System.arraycopy(outBuf, length, @@ -326,22 +333,23 @@ private boolean flushOutBuf(ByteBuffer out) { remainder); } outBufPos = remainder; - + if (length == outRemaining) { assert out.remaining() == 0; fullOut = true; } - + return (remainder != 0); } - - private static final byte zeroByte = (byte) '0'; + + private static final byte ZERO_BYTE = (byte) '0'; private static final byte[] hexadecimalChars = UtilConvert.hexDigit; - + + @SuppressWarnings("ValueOfIncrementOrDecrementUsed") private int encodeChar(final char c) { final int oldPos = outBufPos; final int cInt = (int) c; - + if ((c == '\r') || (c == '\n') || (c == '\t') || (c == '\f')) { /* * Do not translate Space, Tab and FF characters @@ -356,27 +364,27 @@ private int encodeChar(final char c) { outBuf[outBufPos++] = hexadecimalChars[(cInt >> 12) & 0x000f]; outBuf[outBufPos++] = hexadecimalChars[(cInt >> 8) & 0x000f]; } else { - outBuf[outBufPos++] = zeroByte; - outBuf[outBufPos++] = zeroByte; + outBuf[outBufPos++] = ZERO_BYTE; + outBuf[outBufPos++] = ZERO_BYTE; } outBuf[outBufPos++] = hexadecimalChars[(cInt >> 4) & 0x000f]; outBuf[outBufPos++] = hexadecimalChars[cInt & 0x000f]; } else { outBuf[outBufPos++] = (byte) c; } - + return outBufPos - oldPos; } - + byte[] encodeCharForTests(final char c) { reset(); - + final int tokenLength = encodeChar(c); byte[] result = new byte[tokenLength]; System.arraycopy(outBuf, 0, result, 0, tokenLength); return result; } - + byte[] encodeStringForTests(final String s) throws CharacterCodingException { ByteBuffer resultBuf = encode(CharBuffer.wrap(s)); byte[] resultBufArray = resultBuf.array(); @@ -389,14 +397,13 @@ byte[] encodeStringForTests(final String s) throws CharacterCodingException { return result; } } - + } - /** - * - */ + + @SuppressWarnings("PackageVisibleInnerClass") // For unittests static final class PropCharsetDecoder extends CharsetDecoder { - + private final Logger log = Logger.getLogger(getClass().getName().replace('$', '.')); private static enum State { @@ -404,10 +411,10 @@ private static enum State { BACKSLASH, UNICODE, } - - private static final float avgCharsPerByte = 1.00f; - private static final float maxCharsPerByte = 6.00f; - private static final int maxCharsPerByteInt = 6; + + private static final float AVG_CHARS_PER_BYTE = 1.00f; + private static final float MAX_CHARS_PER_BYTE = 6.00f; + private static final int MAX_CHARS_PER_BYTE_INT = 6; /* * Five chars are written to the output when a malformed unicode * sequence is detected. Unicode sequences are six bytes long; @@ -418,9 +425,9 @@ private static enum State { * (The sixth byte is re-read and handled in the next round * of the decoding cycle.) */ - - private static final int inBufSize = 8192; - private static final int outBufSize = inBufSize; + + private static final int IN_BUF_SIZE = 8192; + private static final int OUT_BUF_SIZE = IN_BUF_SIZE; /** */ private static final int SIZE_UNKNOWN = -1; @@ -428,32 +435,32 @@ private static enum State { private long inputSize; /** number of input bytes decoded so far */ private int bytesDecoded = 0; - - private final byte[] inBuf = new byte[inBufSize]; - private final char[] outBuf = new char[outBufSize]; - + + private final byte[] inBuf = new byte[IN_BUF_SIZE]; + private final char[] outBuf = new char[OUT_BUF_SIZE]; + private int inBufPos, outBufPos; - + private boolean emptyIn; private boolean fullOut; private boolean emptyInBuf; - + private State state; private int unicodeBytesRead; private int unicodeValue; - + /** used when flushing a unicode sequence to the out buffer */ - private char[] unicodeValueChars = new char[4]; - + private final char[] unicodeValueChars = new char[4]; + PropCharsetDecoder(Charset charset) { this(charset, SIZE_UNKNOWN); } - + PropCharsetDecoder(Charset charset, long inputSize) { - super(charset, avgCharsPerByte, maxCharsPerByte); + super(charset, AVG_CHARS_PER_BYTE, MAX_CHARS_PER_BYTE); this.inputSize = inputSize; } - + { implReset(); } @@ -465,28 +472,26 @@ protected void implReset() { inputSize = SIZE_UNKNOWN; bytesDecoded = 0; - + inBufPos = 0; outBufPos = 0; - + emptyIn = false; fullOut = false; emptyInBuf = true; - + state = State.INITIAL; unicodeBytesRead = 0; } + @Override + @SuppressWarnings("LoggerStringConcat") // usage is guarded by Logger#isLoggable protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { log.finer(""); log.finer("decodeLoop() called"); - if (log.isLoggable(Level.FINEST)) { - String inCount = String.format("%5d", in.remaining()); - String outCount = String.format("%5d", out.remaining()); - log.finest(" - input: " + inCount + " bytes"); - log.finest(" - output: " + outCount + " chars"); - } - + log.finest(() -> " - input: " + String.format("%5d", in.remaining()) + " bytes"); + log.finest(() -> " - output: " + String.format("%5d", out.remaining()) + " chars"); + emptyIn = false; fullOut = false; try { @@ -523,24 +528,24 @@ protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { return OVERFLOW; } } - + /** * This is method is only necessary because of bug in JDK 1.5.x that * {@code flush()} is not always called when it should be. * Once JDK 1.5.x is not supported, remove this method. - * + * * @see #handlePendingCharacters() */ private boolean hasPendingCharacters() { return state != State.INITIAL; } - + /** * This is method is only necessary because of bug in JDK 1.5.x that * {@code flush()} is not always called when it should be. * Once JDK 1.5.x is not supported, move this method's functionality * to method {@code implFlush()} and remove this method. - * + * * @see JDK bug 6221056 * @see JDK bug 4744247 */ @@ -550,26 +555,24 @@ private void handlePendingCharacters() { log.finer(" - no pending characters"); return; } - + switch (state) { - case INITIAL: + case INITIAL -> { assert false; - break; - case BACKSLASH: + } + case BACKSLASH -> { log.finer(" - backslash pending"); outBuf[outBufPos++] = '\\'; - break; - case UNICODE: + } + case UNICODE -> { log.finer(" - broken \\u.... sequence pending"); - if (log.isLoggable(Level.FINEST)) { - log.finest(" - " + unicodeBytesRead + " unicode value bytes pending"); - } + log.finest(() -> " - " + unicodeBytesRead + " unicode value bytes pending"); assert (unicodeBytesRead >= 0) && (unicodeBytesRead < 4); flushUnicodeSequence(); - break; - default: + } + default -> { assert false; - break; + } } state = State.INITIAL; } @@ -582,7 +585,7 @@ protected CoderResult implFlush(CharBuffer out) { return flushOutBuf(out) ? OVERFLOW : UNDERFLOW; } - + /** * Reads and stores as many characters from the input buffer as possible. * If there are no more characters available in the input buffer, @@ -594,39 +597,39 @@ private void readIn(ByteBuffer in) { log.finer(" - input empty (emptyIn already set)"); return; } - + int inRemaining = in.remaining(); if (inRemaining == 0) { log.finer(" - input empty (emptyIn will be set)"); emptyIn = true; return; } - + int bufRemaining = inBuf.length - inBufPos; if (bufRemaining == 0) { log.finer(" - no space remaining in inBuf"); /* no space in inBuf */ return; } - + int length = min(inRemaining, bufRemaining); if (log.isLoggable(Level.FINER)) { - log.finer(" - " + length + " bytes will be read"); + log.finer(() -> " - " + length + " bytes will be read"); } in.get(inBuf, inBufPos, length); inBufPos += length; emptyInBuf = false; - + if (length == inRemaining) { assert in.remaining() == 0; log.finer(" - all remaining bytes were read (emptyIn will be set)"); emptyIn = true; } } - + /** * Encodes as many chars from the internal input buffer as possible. - * + * * @return number of bytes decoded */ private int decodeBuf() { @@ -635,12 +638,12 @@ private int decodeBuf() { log.finer(" - inBuf is empty - nothing to decode"); return 0; } - + int decodingInBufPos = 0; log.finest(" - decoding bytes:"); - log.finest(" - initial state: " + state); + log.finest(() -> " - initial state: " + state); while ((decodingInBufPos < inBufPos) - && (outBufPos <= outBufSize - maxCharsPerByteInt)) { + && (outBufPos <= OUT_BUF_SIZE - MAX_CHARS_PER_BYTE_INT)) { int decodedChars = decodeByte(inBuf[decodingInBufPos++]); if (log.isLoggable(Level.FINEST)) { StringBuilder sb = new StringBuilder(60); @@ -663,7 +666,7 @@ private int decodeBuf() { int remainder = inBufPos - decodingInBufPos; if (remainder != 0) { if (log.isLoggable(Level.FINER)) { - log.finer(" - " + remainder + " bytes will remain in the inBuf"); + log.finer(() -> " - " + remainder + " bytes will remain in the inBuf"); } System.arraycopy(inBuf, decodingInBufPos, inBuf, 0, @@ -675,9 +678,9 @@ private int decodeBuf() { emptyInBuf = (inBufPos == 0); return decodingInBufPos; } - + /** - * + * * @return {@code true} if the given {@code out} buffer is overflown, * {@code false} otherwise */ @@ -688,29 +691,29 @@ private boolean flushOutBuf(CharBuffer out) { log.finer(" - outBuf is empty - nothing to flush"); return false; } - + if (fullOut) { //we know that (outBufPos != null) log.finer(" - output CharBuffer is full (fullOut already set)"); return true; } - + int outRemaining = out.remaining(); if (outRemaining == 0) { log.finer(" - output CharBuffer is full (fullOut will be set)"); fullOut = true; return true; } - + int length = min(outRemaining, outBufPos); if (log.isLoggable(Level.FINER)) { - log.finer(" - " + length + " chars will be written"); + log.finer(() -> " - " + length + " chars will be written"); } out.put(outBuf, 0, length); - + int remainder = outBufPos - length; if (remainder != 0) { if (log.isLoggable(Level.FINER)) { - log.finer(" - " + remainder + " bytes will remain in the outBuf"); + log.finer(() -> " - " + remainder + " bytes will remain in the outBuf"); } System.arraycopy(outBuf, length, outBuf, 0, @@ -719,36 +722,36 @@ private boolean flushOutBuf(CharBuffer out) { log.finer(" - all bytes were successfully flushed"); } outBufPos = remainder; - + if (length == outRemaining) { assert out.remaining() == 0; log.finer(" - output CharBuffer is now full (fullOut will be set)"); fullOut = true; } - + return (remainder != 0); } - - private static final String hexadecimalChars - = "0123456789abcdefABCDEF"; //NOI18N - + + private static final String HEXADECIMAL_CHARS = "0123456789abcdefABCDEF"; //NOI18N + + @SuppressWarnings("ValueOfIncrementOrDecrementUsed") private int decodeByte(final byte b) { final int oldPos = outBufPos; final int bInt = (b >= 0 ? b : b + 256); assert (bInt >= 0) && ((bInt & 0xff) == bInt); - + final char bChar = (char) bInt; - + switch (state) { - case INITIAL: + case INITIAL -> { if (bChar == '\\') { state = State.BACKSLASH; } else { outBuf[outBufPos++] = bChar; /* keep the state at INITIAL */ } - break; - case BACKSLASH: + } + case BACKSLASH -> { if (bChar == 'u') { state = State.UNICODE; } else { @@ -756,10 +759,10 @@ private int decodeByte(final byte b) { outBuf[outBufPos++] = bChar; state = State.INITIAL; } - break; - case UNICODE: + } + case UNICODE -> { boolean malformed = false; - int index = hexadecimalChars.indexOf(bChar); + int index = HEXADECIMAL_CHARS.indexOf(bChar); if (index >= 0) { if (index > 15) { //one of [A-F] used index -= 6; //transform to lowercase @@ -770,13 +773,13 @@ private int decodeByte(final byte b) { if (unicodeValue <= 0x20) { unicodeValueChars[3] = bChar; /* - * Do not translate Unicode sequences of value - * 20h (space) or less (control characters). - * Changing form of these unicode sequences - * to one-character form might change - * semantics, which is not desirable - * (see issue #111530). - */ + * Do not translate Unicode sequences of value + * 20h (space) or less (control characters). + * Changing form of these unicode sequences + * to one-character form might change + * semantics, which is not desirable + * (see issue #111530). + */ flushUnicodeSequence(); } else { outBuf[outBufPos++] = (char) unicodeValue; @@ -789,8 +792,8 @@ private int decodeByte(final byte b) { } else { malformed = true; /* - * send the malformed unicode sequence to the output - */ + * send the malformed unicode sequence to the output + */ flushUnicodeSequence(); state = State.INITIAL; } @@ -801,12 +804,12 @@ private int decodeByte(final byte b) { return -1; } } - break; - default: + } + default -> { assert false; - break; + } } - + return outBufPos - oldPos; } @@ -816,6 +819,7 @@ private int decodeByte(final byte b) { * It also resets fields {@link #unicodeBytesRead} * and {@link #unicodeValue} to {@code 0}. */ + @SuppressWarnings("ValueOfIncrementOrDecrementUsed") private void flushUnicodeSequence() { outBuf[outBufPos++] = '\\'; outBuf[outBufPos++] = 'u'; @@ -825,15 +829,15 @@ private void flushUnicodeSequence() { unicodeBytesRead = 0; unicodeValue = 0; } - + private static char[] hexavalue(byte b) { final int bInt = (b >= 0 ? b : b + 256); char[] result = new char[2]; - result[0] = hexadecimalChars.charAt(bInt / 16); - result[1] = hexadecimalChars.charAt(bInt % 16); + result[0] = HEXADECIMAL_CHARS.charAt(bInt / 16); + result[1] = HEXADECIMAL_CHARS.charAt(bInt % 16); return result; } - + char[] decodeBytesForTests(final byte[] bytes) throws CharacterCodingException { CharBuffer resultBuf = decode(ByteBuffer.wrap(bytes)); char[] resultBufArray = resultBuf.array(); @@ -846,7 +850,7 @@ char[] decodeBytesForTests(final byte[] bytes) throws CharacterCodingException { return result; } } - + } } From 5210b10a38994a0a51a46231e8452c01d65ed09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Thu, 22 Oct 2026 18:41:44 +0200 Subject: [PATCH 3/5] Simplify PropCharset by removing association with the FileObject --- .../properties/PropertiesEncoding.java | 108 +----------------- 1 file changed, 6 insertions(+), 102 deletions(-) diff --git a/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java b/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java index aea778049fc1..89aa52bf3325 100644 --- a/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java +++ b/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java @@ -19,9 +19,6 @@ package org.netbeans.modules.properties; -import java.lang.ref.Reference; -import java.lang.ref.WeakReference; -import java.net.URL; import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; @@ -34,16 +31,11 @@ import java.util.logging.Level; import java.util.logging.Logger; import org.netbeans.spi.queries.FileEncodingQueryImplementation; -import org.openide.filesystems.FileAttributeEvent; -import org.openide.filesystems.FileChangeListener; -import org.openide.filesystems.FileEvent; import org.openide.filesystems.FileObject; + import static java.lang.Math.min; import static java.nio.charset.CoderResult.OVERFLOW; import static java.nio.charset.CoderResult.UNDERFLOW; -import org.openide.filesystems.FileRenameEvent; -import org.openide.filesystems.FileStateInvalidException; -import org.openide.filesystems.URLMapper; /** * @@ -70,30 +62,14 @@ public Charset getEncoding(FileObject file) { return null; } } - try { - return new PropCharset(file); - } catch (FileStateInvalidException ex) { - return null; - } + return new PropCharset(); } @SuppressWarnings("PackageVisibleInnerClass") // For unittests - static final class PropCharset extends Charset implements FileChangeListener { - - private final Reference fileRef; - private URL fileURL; - - @SuppressWarnings("LeakingThisInConstructor") - PropCharset(FileObject file) throws FileStateInvalidException { - super(PROP_CHARSET_NAME, null); //NOI18N - fileRef = new WeakReference<>(file); - file.addFileChangeListener(this); - updateURL(file); - } + static final class PropCharset extends Charset { PropCharset() { super(PROP_CHARSET_NAME, null); //NOI18N - fileRef = null; } @Override @@ -108,60 +84,7 @@ public CharsetEncoder newEncoder() { @Override public CharsetDecoder newDecoder() { - long fileSize = (fileRef != null) ? getFileSize() : -1; - return (fileSize > 0l) ? new PropCharsetDecoder(this, fileSize) - : new PropCharsetDecoder(this); - } - - private long getFileSize() { - FileObject file = getFile(); - return ((file != null) && file.isValid()) ? file.getSize() : 0l; - } - - private FileObject getFile() { - FileObject fileObj = fileRef.get(); - - URL url; - synchronized (this) { - url = fileURL; - } - - if ((fileObj == null) && (url != null)) { - fileObj = URLMapper.findFileObject(url); - } - return fileObj; - } - - @Override - public void fileRenamed(FileRenameEvent fe) { - updateURL(fe.getFile()); - } - - @Override - public void fileChanged(FileEvent fe) { - updateURL(fe.getFile()); - } - - private synchronized void updateURL(FileObject file) { - fileURL = file.toURL(); - } - - @Override - public void fileDeleted(FileEvent fe) { } - - @Override - public void fileAttributeChanged(FileAttributeEvent fe) { } - - @Override - public void fileDataCreated(FileEvent fe) { - /* this should be never called on plain files (non-directories) */ -// assert false; - } - - @Override - public void fileFolderCreated(FileEvent fe) { - /* this should be never called on plain files (non-directories) */ - assert false; + return new PropCharsetDecoder(this); } } @@ -429,13 +352,6 @@ private static enum State { private static final int IN_BUF_SIZE = 8192; private static final int OUT_BUF_SIZE = IN_BUF_SIZE; - /** */ - private static final int SIZE_UNKNOWN = -1; - /** size of the input file, or {@link #SIZE_UNKNOWN} if unknown */ - private long inputSize; - /** number of input bytes decoded so far */ - private int bytesDecoded = 0; - private final byte[] inBuf = new byte[IN_BUF_SIZE]; private final char[] outBuf = new char[OUT_BUF_SIZE]; @@ -453,12 +369,7 @@ private static enum State { private final char[] unicodeValueChars = new char[4]; PropCharsetDecoder(Charset charset) { - this(charset, SIZE_UNKNOWN); - } - - PropCharsetDecoder(Charset charset, long inputSize) { super(charset, AVG_CHARS_PER_BYTE, MAX_CHARS_PER_BYTE); - this.inputSize = inputSize; } { @@ -470,9 +381,6 @@ protected void implReset() { log.finer(""); log.finer("implReset() called"); - inputSize = SIZE_UNKNOWN; - bytesDecoded = 0; - inBufPos = 0; outBufPos = 0; @@ -499,15 +407,11 @@ protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) { for (;;) { readIn(in); for (;;) { - bytesDecoded += decodeBuf(); - - // assert: if (bytesDecoded == inputSize) then (emptyIn) - assert (bytesDecoded != inputSize) || emptyIn; + decodeBuf(); if (emptyInBuf && !emptyIn) { continue readInLoop; - } else if (emptyIn && hasPendingCharacters() - && ((inputSize == SIZE_UNKNOWN) || (bytesDecoded >= inputSize))) { + } else if (emptyIn && hasPendingCharacters()) { handlePendingCharacters(); } flushOutBuf(out); From 6f0624d2d30814544d93a345f3ebd57c02db917b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Thu, 22 Oct 2026 18:49:11 +0200 Subject: [PATCH 4/5] Remove no-op methods from UtilConvert --- .../org-netbeans-modules-properties.sig | 13 +- .../properties/PropertiesEncoding.java | 4 +- .../modules/properties/UtilConvert.java | 269 ------------------ .../modules/i18n/java/JavaI18nFinder.java | 4 +- 4 files changed, 6 insertions(+), 284 deletions(-) diff --git a/ide/properties/nbproject/org-netbeans-modules-properties.sig b/ide/properties/nbproject/org-netbeans-modules-properties.sig index f8b16e987552..0410dc51dc76 100644 --- a/ide/properties/nbproject/org-netbeans-modules-properties.sig +++ b/ide/properties/nbproject/org-netbeans-modules-properties.sig @@ -1,5 +1,5 @@ #Signature file v4.1 -#Version 1.88 +#Version 1.90 CLSS public abstract java.awt.Component cons protected init() @@ -1241,7 +1241,7 @@ meth public void open() meth public void saveAs(org.openide.filesystems.FileObject,java.lang.String) throws java.io.IOException supr org.openide.text.CloneableEditorSupport hfds LOG,charset,fsStatusListener,myEntry,serialVersionUID -hcls Env,Environment,EnvironmentListener,FsStatusListener,PropertiesEditorLookup,SaveImpl,StampFlag,UndoRedoStampFlagManager +hcls Environment,EnvironmentListener,FsStatusListener,PropertiesEditorLookup,SaveImpl,StampFlag,UndoRedoStampFlagManager CLSS public org.netbeans.modules.properties.PropertiesEditorSupport$PropertiesEditAt outer org.netbeans.modules.properties.PropertiesEditorSupport @@ -1520,16 +1520,7 @@ CLSS public org.netbeans.modules.properties.UtilConvert fld public final static java.lang.String keyValueSeparators = "=: \u0009\r\n\u000c" fld public final static java.lang.String strictKeyValueSeparators = "=:" fld public final static java.lang.String whiteSpaceChars = " \u0009\r\n\u000c" -meth public static java.lang.String charsToUnicodes(java.lang.String) -meth public static java.lang.String charsToUnicodes(java.lang.String,boolean) -meth public static java.lang.String escapeComment(java.lang.String) -meth public static java.lang.String escapeJavaSpecialChars(java.lang.String) -meth public static java.lang.String escapeLineContinuationChar(java.lang.String) -meth public static java.lang.String escapeOutsideSpaces(java.lang.String) -meth public static java.lang.String escapePropertiesSpecialChars(java.lang.String) meth public static java.lang.String loadConvert(java.lang.String) -meth public static java.lang.String saveConvert(java.lang.String) -meth public static java.lang.String unicodesToChars(java.lang.String) supr java.lang.Object hfds hexDigit,specialSaveChars diff --git a/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java b/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java index 89aa52bf3325..a25de4054aef 100644 --- a/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java +++ b/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java @@ -266,7 +266,9 @@ private boolean flushOutBuf(ByteBuffer out) { } private static final byte ZERO_BYTE = (byte) '0'; - private static final byte[] hexadecimalChars = UtilConvert.hexDigit; + private static final byte[] hexadecimalChars = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f'}; // #168629 @SuppressWarnings("ValueOfIncrementOrDecrementUsed") private int encodeChar(final char c) { diff --git a/ide/properties/src/org/netbeans/modules/properties/UtilConvert.java b/ide/properties/src/org/netbeans/modules/properties/UtilConvert.java index 8d2eb94f0ca5..3f52c1dd30d9 100644 --- a/ide/properties/src/org/netbeans/modules/properties/UtilConvert.java +++ b/ide/properties/src/org/netbeans/modules/properties/UtilConvert.java @@ -45,223 +45,8 @@ private UtilConvert() { public static final String strictKeyValueSeparators = "=:"; - /** Differs from JDK's implementation in that it does not save ' ' as '\ '. */ - private static final String specialSaveChars = "=:\t\r\n\f#!"; - public static final String whiteSpaceChars = " \t\r\n\f"; - - /** - * Escape key value. Converts string to one with escaped ' ','=',':', and last '\\' - * in case they are not escaped already. Used for formating user input. - * //!!! wrong semantics what does it mean if not escaped already? - */ - public static String escapePropertiesSpecialChars (String source) { - return source; // prj40 trunk compatability -// if(source == null) return null; -// StringBuffer result = new StringBuffer(); -// for(int i=0; i0 && source.charAt(i-1) != '\\')) -// result.append('\\'); -// } -// // last char == '\\' -// if(i==source.length()-1 && x == '\\') { -// if( i>0 && source.charAt(i-1)!='\\') -// result.append('\\'); -// } -// result.append(x); -// } -// return result.toString(); - } - - /** Checks whether the string contains only spaces */ - private static boolean onlySpaces(String s){ - for (int i = 0; i1 && source.charAt(source.length()-2)!='\\') -// return new String(new StringBuffer(source).append('\\')); -// } -// return source; - } - - /** - * Converts these java special chars ('\t', '\n', '\b', '\r', '\f') to encoded escapes. - * Note there are not converted unicode chars. - */ - public static String escapeJavaSpecialChars(String source) { - return source; // prj40 trunk compatability -// if(source == null) return null; -// StringBuffer result = new StringBuffer(); -// for (int i=0; i interpret as a normal char -// } -// } else -// outBuffer.append(aChar); -// } -// return outBuffer.toString(); - } - - /** - * Convert chars to encoded '\\uxxxx' using comment escaping rules. - * @param commentString - * @return escaped comment - */ - public static String escapeComment(String commentString) { - return charsToUnicodes(commentString, true); - } - - /** - * Converts chars to encoded '\\uxxxx'. - * Note there are not converted '\\"', '\\'', '\\ ', '\\\\' and java special chars escapes. - */ - public static String charsToUnicodes(String s){ - return charsToUnicodes(s, false); - } - - - /** - * Converts chars to encoded '\\uxxxx'. If skipWhiteSpaces is true, then white spaces won't be converted - * Note there are not converted '\\"', '\\'', '\\ ', '\\\\' and java special chars escapes. - */ - public static String charsToUnicodes(String theString, boolean skipWhiteSpaces) { - return theString; // prj40 trunk compatability -// if(theString == null) return null; -// char aChar; -// int len = theString.length(); -// StringBuffer outBuffer = new StringBuffer(len*2); -// -// for(int x=0; x 127) ) { -// -// if (skipWhiteSpaces && Character.isWhitespace(aChar)){ -// // do not convert white spaces -// outBuffer.append(aChar); -// continue; -// } -// -// outBuffer.append('\\'); -// outBuffer.append('u'); -// outBuffer.append(toHex((aChar >> 12) & 0xF)); -// outBuffer.append(toHex((aChar >> 8) & 0xF)); -// outBuffer.append(toHex((aChar >> 4) & 0xF)); -// outBuffer.append(toHex((aChar >> 0) & 0xF)); -// } else { -// outBuffer.append(aChar); -// } -// } -// return outBuffer.toString(); - } - - /** * Converts encoded \\uxxxx to unicode chars * and changes special saved chars to their original forms @@ -329,58 +114,4 @@ public static String loadConvert (String theString) { return outBuffer.toString(); } - /** - * Converts unicodes to encoded \\uxxxx - * and writes out any of the characters in specialSaveChars - * with a preceding slash. - * Differs from Sun's implementation in that it does not save ' ' as '\ '. - */ - public static String saveConvert(String theString) { - char aChar; - int len = theString.length(); - StringBuffer outBuffer = new StringBuffer(len*2); - - for(int x=0; x 127)) { - outBuffer.append('\\'); - outBuffer.append('u'); - outBuffer.append(toHex((aChar >> 12) & 0xF)); - outBuffer.append(toHex((aChar >> 8) & 0xF)); - outBuffer.append(toHex((aChar >> 4) & 0xF)); - outBuffer.append(toHex((aChar >> 0) & 0xF)); - } else { - if (specialSaveChars.indexOf(aChar) != -1) - outBuffer.append('\\'); - outBuffer.append(aChar); - } - } - } - return outBuffer.toString(); - } - - /** - * Convert a nibble to a hex character - * @param nibble the nibble to convert. - */ - private static char toHex(int nibble) { - return (char)hexDigit[(nibble & 0xF)]; - } - - /** A table of hex digits */ - static final byte[] hexDigit = { - '0','1','2','3','4','5','6','7','8','9', - 'a','b','c','d','e','f'}; // #168629 } diff --git a/java/i18n/src/org/netbeans/modules/i18n/java/JavaI18nFinder.java b/java/i18n/src/org/netbeans/modules/i18n/java/JavaI18nFinder.java index 5dd8ab510e8e..201095485d13 100644 --- a/java/i18n/src/org/netbeans/modules/i18n/java/JavaI18nFinder.java +++ b/java/i18n/src/org/netbeans/modules/i18n/java/JavaI18nFinder.java @@ -565,13 +565,11 @@ private String extractString(String sourceString) { * @return true if string is internationalized and i18nSearch flag is true * or if if string is non-internationalized and i18nSearch flag is false */ protected boolean isSearchedString(String partHardLine, String hardString) { - String lineToMatch = UtilConvert.unicodesToChars(partHardLine); - Boolean regexpTestResult; Exception ex = null; try { String regexp = createRegularExpression(hardString); - regexpTestResult = (Pattern.compile(regexp).matcher(lineToMatch).find() + regexpTestResult = (Pattern.compile(regexp).matcher(partHardLine).find() == i18nSearch); //auto-boxing } catch (ParseException ex1) { ex = ex1; From dd250abb03705837b350463ed8318864dafa0555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Bl=C3=A4sing?= Date: Sat, 24 Oct 2026 13:46:40 +0200 Subject: [PATCH 5/5] Property-Files: use native encoding by default and allow project/IDE wide configuration Traditionally property files for Java used Latin1/ASCII encoding. Characters outside the Latin1-Range had to be encoded as unicode escapes. The Euro-Sign for example had to be written as \u20ac. Since JDK 9 ResourceBundle defaults to UTF-8 for reading property files, rendering escape codes useless. This aligns NetBeans with other editors, that never got the comfort of transparent UniCode input <-> escape encoding. It was always possible to switch between native encoding and using escapes on a per file basis, but for large codebases this is a tedious process. To support mixed setups, the encoding variant to use can now be configured in the "Editor" -> "Formatting" options. This can be done in the options dialog globally and in the project dialog on a per project basis. --- ide/options.editor/nbproject/project.xml | 1 + ide/properties/nbproject/project.xml | 54 +- .../org/netbeans/modules/properties/Layer.xml | 16 + .../properties/PropertiesDataNode.java | 20 +- .../properties/PropertiesEncoding.java | 15 +- .../properties/formatter/Bundle.properties | 37 ++ .../properties/formatter/CodeStyle.java | 80 +++ .../properties/formatter/FmtEncoding.form | 89 +++ .../properties/formatter/FmtEncoding.java | 105 +++ .../properties/formatter/FmtOptions.java | 599 ++++++++++++++++++ 10 files changed, 1002 insertions(+), 14 deletions(-) create mode 100644 ide/properties/src/org/netbeans/modules/properties/formatter/Bundle.properties create mode 100644 ide/properties/src/org/netbeans/modules/properties/formatter/CodeStyle.java create mode 100644 ide/properties/src/org/netbeans/modules/properties/formatter/FmtEncoding.form create mode 100644 ide/properties/src/org/netbeans/modules/properties/formatter/FmtEncoding.java create mode 100644 ide/properties/src/org/netbeans/modules/properties/formatter/FmtOptions.java diff --git a/ide/options.editor/nbproject/project.xml b/ide/options.editor/nbproject/project.xml index 4f97c52683e1..f48eef566007 100644 --- a/ide/options.editor/nbproject/project.xml +++ b/ide/options.editor/nbproject/project.xml @@ -372,6 +372,7 @@ org.netbeans.modules.php.editor org.netbeans.modules.php.project org.netbeans.modules.php.twig + org.netbeans.modules.properties org.netbeans.modules.python.editor org.netbeans.modules.python.source org.netbeans.modules.ruby diff --git a/ide/properties/nbproject/project.xml b/ide/properties/nbproject/project.xml index c366b88bb912..95722f9d99aa 100644 --- a/ide/properties/nbproject/project.xml +++ b/ide/properties/nbproject/project.xml @@ -34,6 +34,50 @@ 1.25 + + org.netbeans.modules.editor + + + + 3 + 1.122 + + + + org.netbeans.modules.editor.document + + + + 1.44 + + + + org.netbeans.modules.editor.indent + + + + 2 + 1.78 + + + + org.netbeans.modules.editor.settings + + + + 1 + 1.92 + + + + org.netbeans.modules.options.editor + + + + 1 + 1.96 + + org.netbeans.modules.queries @@ -100,7 +144,7 @@ - org.openide.util.ui + org.openide.util @@ -108,19 +152,19 @@ - org.openide.util + org.openide.util.lookup - 9.3 + 8.0 - org.openide.util.lookup + org.openide.util.ui - 8.0 + 9.3 diff --git a/ide/properties/src/org/netbeans/modules/properties/Layer.xml b/ide/properties/src/org/netbeans/modules/properties/Layer.xml index d1198ada2e67..774fe605d9e9 100644 --- a/ide/properties/src/org/netbeans/modules/properties/Layer.xml +++ b/ide/properties/src/org/netbeans/modules/properties/Layer.xml @@ -132,4 +132,20 @@ + + + + + + + + + + + + + + + + diff --git a/ide/properties/src/org/netbeans/modules/properties/PropertiesDataNode.java b/ide/properties/src/org/netbeans/modules/properties/PropertiesDataNode.java index 0828618d3861..f4fb47d47aa0 100644 --- a/ide/properties/src/org/netbeans/modules/properties/PropertiesDataNode.java +++ b/ide/properties/src/org/netbeans/modules/properties/PropertiesDataNode.java @@ -330,11 +330,23 @@ public ProjectEncodingProperty() { } @Override - public Object getValue() throws InvocationTargetException { + public boolean isDefaultValue() { + return getValue() == null; + } + + @Override + public void restoreDefaultValue() throws IllegalAccessException, InvocationTargetException { + setValue(null); + } + + @Override + public boolean supportsDefaultValue() { + return true; + } + + @Override + public Object getValue() { Object attribute = getDataObject().getPrimaryFile().getAttribute(PROPERTY_ENCODING); - if (attribute == null) { - return false; - } return attribute; } diff --git a/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java b/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java index a25de4054aef..0f537d7d7383 100644 --- a/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java +++ b/ide/properties/src/org/netbeans/modules/properties/PropertiesEncoding.java @@ -30,6 +30,7 @@ import java.nio.charset.CoderResult; import java.util.logging.Level; import java.util.logging.Logger; +import org.netbeans.modules.properties.formatter.CodeStyle; import org.netbeans.spi.queries.FileEncodingQueryImplementation; import org.openide.filesystems.FileObject; @@ -56,11 +57,15 @@ public final class PropertiesEncoding extends FileEncodingQueryImplementation { public Charset getEncoding(FileObject file) { assert !file.isValid() || file.isData(); Object attribute = file.getAttribute(PropertiesDataNode.PROPERTY_ENCODING); - if (attribute != null) { - boolean useProjectEncoding = (Boolean) attribute; - if (useProjectEncoding) { - return null; - } + Boolean useProjectEncoding = null; + if (attribute instanceof Boolean) { + useProjectEncoding = (Boolean) attribute; + } + if (useProjectEncoding == null) { + useProjectEncoding = CodeStyle.get(file).useProjectEncoding(); + } + if (useProjectEncoding) { + return null; } return new PropCharset(); } diff --git a/ide/properties/src/org/netbeans/modules/properties/formatter/Bundle.properties b/ide/properties/src/org/netbeans/modules/properties/formatter/Bundle.properties new file mode 100644 index 000000000000..df91ff627983 --- /dev/null +++ b/ide/properties/src/org/netbeans/modules/properties/formatter/Bundle.properties @@ -0,0 +1,37 @@ +# 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. +AN_Preview=Preview +# 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. +FmtEncoding.useProjectEncodingCB.text=Use project encoding +FmtEncoding.useProjectEncodingCB.info=Disables automatic native2ascii conversion +FmtWrapping.AccessibleContext.accessibleName= +FmtEncoding.name=Encoding diff --git a/ide/properties/src/org/netbeans/modules/properties/formatter/CodeStyle.java b/ide/properties/src/org/netbeans/modules/properties/formatter/CodeStyle.java new file mode 100644 index 000000000000..1dc510781b0a --- /dev/null +++ b/ide/properties/src/org/netbeans/modules/properties/formatter/CodeStyle.java @@ -0,0 +1,80 @@ +/* + * 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.formatter; + +import java.util.prefs.Preferences; + +import javax.swing.text.Document; +import org.netbeans.modules.editor.indent.spi.CodeStylePreferences; +import org.openide.filesystems.FileObject; + +import static org.netbeans.modules.properties.formatter.FmtOptions.expandTabToSpaces; +import static org.netbeans.modules.properties.formatter.FmtOptions.indentSize; +import static org.netbeans.modules.properties.formatter.FmtOptions.rightMargin; +import static org.netbeans.modules.properties.formatter.FmtOptions.spacesPerTab; +import static org.netbeans.modules.properties.formatter.FmtOptions.tabSize; +import static org.netbeans.modules.properties.formatter.FmtOptions.useProjectEncoding; + +/** + * Adapted from CodeStyle in Javascript2 Editor + */ +public final class CodeStyle { + + private final Preferences preferences; + + private CodeStyle(Preferences preferences) { + this.preferences = preferences; + } + + public static CodeStyle get(Document doc) { + return new CodeStyle(CodeStylePreferences.get(doc).getPreferences()); + } + + public static CodeStyle get(FileObject doc) { + return new CodeStyle(CodeStylePreferences.get(doc).getPreferences()); + } + + // General tabs and indents ------------------------------------------------ + + public boolean expandTabToSpaces() { + return preferences.getBoolean(expandTabToSpaces, FmtOptions.getDefaultAsBoolean(expandTabToSpaces)); + } + + public int getTabSize() { + return preferences.getInt(tabSize, FmtOptions.getDefaultAsInt(tabSize)); + } + + public int getIndentSize() { + return preferences.getInt(indentSize, FmtOptions.getDefaultAsInt(indentSize)); + } + + public int getSpacesPerTab() { + return preferences.getInt(spacesPerTab, FmtOptions.getDefaultAsInt(spacesPerTab)); + } + + public int getRightMargin() { + return preferences.getInt(rightMargin, FmtOptions.getDefaultAsInt(rightMargin)); + } + + public boolean useProjectEncoding() { + return preferences.getBoolean(useProjectEncoding, FmtOptions.getDefaultAsBoolean(useProjectEncoding)); + } + +} diff --git a/ide/properties/src/org/netbeans/modules/properties/formatter/FmtEncoding.form b/ide/properties/src/org/netbeans/modules/properties/formatter/FmtEncoding.form new file mode 100644 index 000000000000..a670ea70316f --- /dev/null +++ b/ide/properties/src/org/netbeans/modules/properties/formatter/FmtEncoding.form @@ -0,0 +1,89 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/ide/properties/src/org/netbeans/modules/properties/formatter/FmtEncoding.java b/ide/properties/src/org/netbeans/modules/properties/formatter/FmtEncoding.java new file mode 100644 index 000000000000..e994ad5ddc9b --- /dev/null +++ b/ide/properties/src/org/netbeans/modules/properties/formatter/FmtEncoding.java @@ -0,0 +1,105 @@ +/* + * 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.formatter; + + +import org.netbeans.modules.options.editor.spi.PreferencesCustomizer; +import org.netbeans.modules.properties.formatter.FmtOptions.CategorySupport; + +import static org.netbeans.modules.properties.formatter.FmtOptions.CategorySupport.OPTION_ID; +import static org.netbeans.modules.properties.formatter.FmtOptions.useProjectEncoding; + + +public class FmtEncoding extends javax.swing.JPanel { + + /** Creates new form FmtEncoding */ + @SuppressWarnings("LeakingThisInConstructor") + public FmtEncoding() { + initComponents(); + + useProjectEncodingCB.putClientProperty(OPTION_ID, useProjectEncoding); + } + + public static PreferencesCustomizer.Factory getController() { + String preview = """ + # Comment + key1.title=Demo Title + key2.title=Unicode symbols can be encoded to ASCII + """; + + return new CategorySupport.Factory("encoding", FmtEncoding.class, preview);//NOI18N + } + + /** This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + // //GEN-BEGIN:initComponents + private void initComponents() { + + useProjectEncodingCB = new javax.swing.JCheckBox(); + jLabel1 = new javax.swing.JLabel(); + + setName(org.openide.util.NbBundle.getMessage(FmtEncoding.class, "FmtEncoding.name")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(useProjectEncodingCB, org.openide.util.NbBundle.getMessage(FmtEncoding.class, "FmtEncoding.useProjectEncodingCB.text")); // NOI18N + useProjectEncodingCB.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + useProjectEncodingCBActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(FmtEncoding.class, "FmtEncoding.useProjectEncodingCB.info")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(21, 21, 21) + .addComponent(jLabel1)) + .addComponent(useProjectEncodingCB)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(useProjectEncodingCB) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel1) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void useProjectEncodingCBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_useProjectEncodingCBActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_useProjectEncodingCBActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel jLabel1; + private javax.swing.JCheckBox useProjectEncodingCB; + // End of variables declaration//GEN-END:variables + +} diff --git a/ide/properties/src/org/netbeans/modules/properties/formatter/FmtOptions.java b/ide/properties/src/org/netbeans/modules/properties/formatter/FmtOptions.java new file mode 100644 index 000000000000..1caf1cb0bb7e --- /dev/null +++ b/ide/properties/src/org/netbeans/modules/properties/formatter/FmtOptions.java @@ -0,0 +1,599 @@ +/* + * 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.formatter; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.prefs.AbstractPreferences; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; +import javax.swing.ComboBoxModel; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JEditorPane; +import javax.swing.JPanel; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; +import org.netbeans.api.editor.document.AtomicLockDocument; +import org.netbeans.api.editor.document.LineDocumentUtils; +import org.netbeans.api.editor.settings.SimpleValueNames; +import org.netbeans.modules.editor.indent.api.Reformat; +import org.netbeans.modules.options.editor.spi.PreferencesCustomizer; +import org.netbeans.modules.options.editor.spi.PreviewProvider; +import org.openide.text.CloneableEditorSupport; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; + +/** + * + * @author phrebejk + * + */ +public class FmtOptions { + + private static final Logger LOGGER = Logger.getLogger(FmtOptions.class.getName()); + + public static final String expandTabToSpaces = SimpleValueNames.EXPAND_TABS; + public static final String tabSize = SimpleValueNames.TAB_SIZE; + public static final String spacesPerTab = SimpleValueNames.SPACES_PER_TAB; + public static final String indentSize = SimpleValueNames.INDENT_SHIFT_WIDTH; + public static final String rightMargin = SimpleValueNames.TEXT_LIMIT_WIDTH; + public static final String useProjectEncoding = "useProjectEncoding"; //NOI18N + + + private FmtOptions() {} + + public static int getDefaultAsInt(String key) { + return Integer.parseInt(defaults.get(key)); + } + + public static boolean getDefaultAsBoolean(String key) { + return Boolean.parseBoolean(defaults.get(key)); + } + + public static String getDefaultAsString(String key) { + return defaults.get(key); + } + + // Private section --------------------------------------------------------- + + private static final String TRUE = "true"; // NOI18N + private static final String FALSE = "false"; // NOI18N + + + private static Map defaults; + + static { + createDefaults(); + } + + private static void createDefaults() { + String defaultValues[][] = { + { expandTabToSpaces, TRUE}, //NOI18N + { tabSize, "8"}, //NOI18N + { indentSize, "4"}, //NOI18N + { useProjectEncoding, "true"}, //NOI18N + { rightMargin, "80"}, //NOI18N + }; + + defaults = new HashMap<>(); + + for (java.lang.String[] strings : defaultValues) { + defaults.put(strings[0], strings[1]); + } + + } + + // Support section --------------------------------------------------------- + + public static class CategorySupport implements ActionListener, DocumentListener, PreviewProvider, PreferencesCustomizer { + + public static final String OPTION_ID = "org.netbeans.modules.properties.formatter.FormatingOptions.ID"; + + private static final int LOAD = 0; + private static final int STORE = 1; + private static final int ADD_LISTENERS = 2; + + private final String previewText; + private final String id; + protected final JPanel panel; + private final List components = new LinkedList<>(); + private JEditorPane previewPane; + + private final Preferences preferences; + private final Preferences previewPrefs; + + protected CategorySupport(Preferences preferences, String id, + JPanel panel, String previewText, String[]... forcedOptions) { + this.preferences = preferences; + this.id = id; + this.panel = panel; + this.previewText = previewText != null ? previewText : NbBundle.getMessage(FmtOptions.class, "SAMPLE_Default"); //NOI18N + + // Scan the panel for its components + scan(panel, components); + + // Initialize the preview preferences + Preferences forcedPrefs = new PreviewPreferences(); + for (String[] option : forcedOptions) { + forcedPrefs.put( option[0], option[1]); + } + this.previewPrefs = new ProxyPreferences(preferences, forcedPrefs); + + // Load and hook up all the components + loadFrom(preferences); + addListeners(); + } + + protected void addListeners() { + scan(ADD_LISTENERS, null); + } + + protected void loadFrom(Preferences preferences) { +// loaded = true; + scan(LOAD, preferences); +// loaded = false; + } +// +// public void applyChanges() { +// storeTo(preferences); +// } +// + protected void storeTo(Preferences p) { + scan(STORE, p); + } + + public void notifyChanged() { +// if (loaded) +// return; + storeTo(preferences); + refreshPreview(); + } + + // ActionListener implementation --------------------------------------- + + @Override + public void actionPerformed(ActionEvent e) { + notifyChanged(); + } + + // DocumentListener implementation ------------------------------------- + + @Override + public void insertUpdate(DocumentEvent e) { + notifyChanged(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + notifyChanged(); + } + + @Override + public void changedUpdate(DocumentEvent e) { + notifyChanged(); + } + + // PreviewProvider methods ----------------------------------------------------- + + @Override + public JComponent getPreviewComponent() { + if (previewPane == null) { + previewPane = new JEditorPane(); + previewPane.getAccessibleContext().setAccessibleName(NbBundle.getMessage(FmtOptions.class, "AN_Preview")); //NOI18N + previewPane.setEditorKit(CloneableEditorSupport.getEditorKit("text/x-properties")); + previewPane.setEditable(false); + } + return previewPane; + } + + @Override + public void refreshPreview() { + JEditorPane pane = (JEditorPane) getPreviewComponent(); + try { + int rm = previewPrefs.getInt(rightMargin, getDefaultAsInt(rightMargin)); + pane.putClientProperty("TextLimitLine", rm); //NOI18N + } + catch( NumberFormatException e ) { + // Ignore it + } + + Rectangle visibleRectangle = pane.getVisibleRect(); + pane.setText(previewText); + pane.setIgnoreRepaint(true); + + final Document doc = pane.getDocument(); + final AtomicLockDocument ald = LineDocumentUtils.as(doc, AtomicLockDocument.class); + if (ald != null) { + final Reformat reformat = Reformat.get(doc); + reformat.lock(); + try { + ald.runAtomic(() -> { + try { + reformat.reformat(0, doc.getLength()); + } catch (BadLocationException ble) { + LOGGER.log(Level.WARNING, null, ble); + } + }); + } finally { + reformat.unlock(); + } + } else { + LOGGER.warning(String.format("Can't format %s; it's not BaseDocument.", doc)); //NOI18N + } + pane.setIgnoreRepaint(false); + pane.scrollRectToVisible(visibleRectangle); + pane.repaint(100); + + } + + // PreferencesCustomizer implementation -------------------------------- + + @Override + public JComponent getComponent() { + return panel; + } + + @Override + public String getDisplayName() { + return panel.getName(); + } + + @Override + public String getId() { + return id; + } + + @Override + public HelpCtx getHelpCtx() { + return null; + } + + // PreferencesCustomizer.Factory implementation ------------------------ + + public static final class Factory implements PreferencesCustomizer.Factory { + + private final String id; + private final Class panelClass; + private final String previewText; + private final String[][] forcedOptions; + + public Factory(String id, Class panelClass, + String previewText, String[]... forcedOptions) { + this.id = id; + this.panelClass = panelClass; + this.previewText = previewText; + this.forcedOptions = forcedOptions; + } + + @Override + public PreferencesCustomizer create(Preferences preferences) { + try { + return new CategorySupport(preferences, id, panelClass.getDeclaredConstructor().newInstance(), previewText, forcedOptions); + } catch (RuntimeException | ReflectiveOperationException e) { + LOGGER.log(Level.WARNING, "Exception during creating formatter customiezer", e); + return null; + } + } + } // End of CategorySupport.Factory class + + // Private methods ----------------------------------------------------- + + private void performOperation(int operation, JComponent jc, String optionID, Preferences p) { + switch (operation) { + case LOAD: + loadData(jc, optionID, p); + break; + case STORE: + storeData(jc, optionID, p); + break; + case ADD_LISTENERS: + addListener(jc); + break; + default: + LOGGER.log(Level.WARNING, "Unknown operation value {0}", operation); + break; + } + } + + private void scan(int what, Preferences p ) { + for (JComponent jc : components) { + Object o = jc.getClientProperty(OPTION_ID); + if (o instanceof String) { + performOperation(what, jc, (String)o, p); + } else if (o instanceof String[]) { + for(String oid : (String[])o) { + performOperation(what, jc, oid, p); + } + } + } + } + + private void scan(Container container, List components) { + for (Component c : container.getComponents()) { + if (c instanceof JComponent) { + JComponent jc = (JComponent)c; + Object o = jc.getClientProperty(OPTION_ID); + if (o instanceof String || o instanceof String[]) + components.add(jc); + } + if (c instanceof Container) + scan((Container)c, components); + } + } + + /** Very smart method which tries to set the values in the components correctly + */ + private void loadData( JComponent jc, String optionID, Preferences node ) { + + if ( jc instanceof JTextField ) { + JTextField field = (JTextField)jc; + field.setText( node.get(optionID, getDefaultAsString(optionID)) ); + } + else if ( jc instanceof JCheckBox ) { + JCheckBox checkBox = (JCheckBox)jc; + boolean df = getDefaultAsBoolean(optionID); + checkBox.setSelected( node.getBoolean(optionID, df)); + } + + } + + private void storeData( JComponent jc, String optionID, Preferences node ) { + + if ( jc instanceof JTextField ) { + JTextField field = (JTextField)jc; + + String text = field.getText(); + + // XXX test for numbers + if ( isInteger(optionID) ) { + try { + Integer.parseInt(text); + } catch (NumberFormatException e) { + return; + } + } + + // XXX: watch out, tabSize, spacesPerTab, indentSize and expandTabToSpaces + // fall back on getGlopalXXX() values and not getDefaultAsXXX value, + // which is why we must not remove them. Proper solution would be to + // store formatting preferences to MimeLookup and not use NbPreferences. + // The problem currently is that MimeLookup based Preferences do not support subnodes. + if (!optionID.equals(tabSize) && + !optionID.equals(spacesPerTab) && !optionID.equals(indentSize) && + getDefaultAsString(optionID).equals(text) + ) { + node.remove(optionID); + } else { + node.put(optionID, text); + } + } + else if ( jc instanceof JCheckBox ) { + JCheckBox checkBox = (JCheckBox)jc; + if (!optionID.equals(expandTabToSpaces) && getDefaultAsBoolean(optionID) == checkBox.isSelected()) + node.remove(optionID); + else + node.putBoolean(optionID, checkBox.isSelected()); + } + else if ( jc instanceof JComboBox) { + JComboBox cb = (JComboBox)jc; + ComboItem comboItem = ((ComboItem) cb.getSelectedItem()); + String value = comboItem == null ? getDefaultAsString(optionID) : comboItem.value; + + if (getDefaultAsString(optionID).equals(value)) + node.remove(optionID); + else + node.put(optionID,value); + } + } + + private void addListener( JComponent jc ) { + if ( jc instanceof JTextField ) { + JTextField field = (JTextField)jc; + field.addActionListener(this); + field.getDocument().addDocumentListener(this); + } + else if ( jc instanceof JCheckBox ) { + JCheckBox checkBox = (JCheckBox)jc; + checkBox.addActionListener(this); + } + else if ( jc instanceof JComboBox) { + JComboBox cb = (JComboBox)jc; + cb.addActionListener(this); + } + } + + + private static ComboItem whichItem(String value, ComboBoxModel model) { + + for (int i = 0; i < model.getSize(); i++) { + ComboItem item = (ComboItem)model.getElementAt(i); + if ( value.equals(item.value)) { + return item; + } + } + return null; + } + + private static class ComboItem { + + String value; + String displayName; + + public ComboItem(String value, String key) { + this.value = value; + this.displayName = NbBundle.getMessage(FmtOptions.class, key); + } + + @Override + public String toString() { + return displayName; + } + + } + } + + public static class PreviewPreferences extends AbstractPreferences { + + private final Map map = new HashMap<>(); + + public PreviewPreferences() { + super(null, ""); // NOI18N + } + + @Override + protected void putSpi(String key, String value) { + map.put(key, value); + } + + @Override + protected String getSpi(String key) { + return (String)map.get(key); + } + + @Override + protected void removeSpi(String key) { + map.remove(key); + } + + @Override + protected void removeNodeSpi() throws BackingStoreException { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + protected String[] keysSpi() throws BackingStoreException { + String array[] = new String[map.keySet().size()]; + return map.keySet().toArray( array ); + } + + @Override + protected String[] childrenNamesSpi() throws BackingStoreException { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + protected AbstractPreferences childSpi(String name) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + protected void syncSpi() throws BackingStoreException { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + protected void flushSpi() throws BackingStoreException { + throw new UnsupportedOperationException("Not supported yet."); + } + } + + // read-only, no subnodes + public static final class ProxyPreferences extends AbstractPreferences { + + private final Preferences[] delegates; + + public ProxyPreferences(Preferences... delegates) { + super(null, ""); // NOI18N + this.delegates = delegates; + } + + @Override + protected void putSpi(String key, String value) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + protected String getSpi(String key) { + for(Preferences p : delegates) { + String value = p.get(key, null); + if (value != null) { + return value; + } + } + return null; + } + + @Override + protected void removeSpi(String key) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + protected void removeNodeSpi() throws BackingStoreException { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + protected String[] keysSpi() throws BackingStoreException { + Set keys = new HashSet<>(); + for(Preferences p : delegates) { + keys.addAll(Arrays.asList(p.keys())); + } + return keys.toArray(new String[0]); + } + + @Override + protected String[] childrenNamesSpi() throws BackingStoreException { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + protected AbstractPreferences childSpi(String name) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + protected void syncSpi() throws BackingStoreException { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + protected void flushSpi() throws BackingStoreException { + throw new UnsupportedOperationException("Not supported yet."); + } + } // End of ProxyPreferences class + + public static boolean isInteger(String optionID) { + String value = defaults.get(optionID); + + try { + Integer.parseInt(value); + return true; + } catch (NumberFormatException numberFormatException) { + return false; + } + } +}