@@ -, +, @@ - variant #3 (fixed JG04-JG08, unit test) --- a/dlight.remote.impl/nbproject/project.xml +++ a/dlight.remote.impl/nbproject/project.xml @@ -85,7 +85,7 @@ - 7.36 + 7.56 --- a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileObjectBase.java +++ a/dlight.remote.impl/src/org/netbeans/modules/remote/impl/fs/RemoteFileObjectBase.java @@ -554,6 +554,9 @@ @Override public Object getAttribute(String attrName) { + if (attrName.equals("default-line-separator")) { // NOI18N + return "\n"; // NOI18N + } if (attrName.equals("isRemoteAndSlow")) { // NOI18N return Boolean.TRUE; } --- a/editor.lib/apichanges.xml +++ a/editor.lib/apichanges.xml @@ -107,6 +107,23 @@ + + Provides default line separator + + + + + +

+ Defines name of default line separator property + DEFAULT_LINE_SEPARATOR_PROP. + If property getProperty(DEFAULT_LINE_SEPARATOR_PROP) is defined, it will be used instead System.getProperty("line.separator"). + It will be used by the text editor if saving new content to an initially empty file. +

+
+ + +
Deprecating old formatting API --- a/editor.lib/nbproject/project.properties +++ a/editor.lib/nbproject/project.properties @@ -42,7 +42,7 @@ javac.compilerargs=-Xlint:unchecked javac.source=1.6 -spec.version.base=3.18.0 +spec.version.base=3.19 is.autoload=true javadoc.arch=${basedir}/arch.xml --- a/editor.lib/nbproject/project.xml +++ a/editor.lib/nbproject/project.xml @@ -141,7 +141,7 @@ - 7.13 + 7.56
--- a/editor.lib/src/org/netbeans/editor/BaseDocument.java +++ a/editor.lib/src/org/netbeans/editor/BaseDocument.java @@ -165,6 +165,13 @@ */ public static final String WRITE_LINE_SEPARATOR_PROP = "write-line-separator"; // NOI18N + /** Default line separator property for writing content into files. + * It will be used by the text editor if saving new content to an initially empty file. + * If it not set the platform default line separator is used. + * @since 3.19 + */ + public static final String DEFAULT_LINE_SEPARATOR_PROP = "default-line-separator"; // NOI18N + /** File name property */ public static final String FILE_NAME_PROP = "file-name"; // NOI18N @@ -1386,7 +1393,10 @@ if (!inited) { // Fill line-separator properties String lineSeparator = ReadWriteUtils.findFirstLineSeparator(buffer); if (lineSeparator == null) { - lineSeparator = ReadWriteUtils.getSystemLineSeparator(); + lineSeparator = (String) getProperty(BaseDocument.DEFAULT_LINE_SEPARATOR_PROP); + if (lineSeparator == null) { + lineSeparator = ReadWriteUtils.getSystemLineSeparator(); + } } putProperty(BaseDocument.READ_LINE_SEPARATOR_PROP, lineSeparator); } @@ -1430,7 +1440,10 @@ if (lineSeparator == null) { lineSeparator = (String) getProperty(BaseDocument.READ_LINE_SEPARATOR_PROP); if (lineSeparator == null) { - lineSeparator = ReadWriteUtils.getSystemLineSeparator(); + lineSeparator = (String) getProperty(BaseDocument.DEFAULT_LINE_SEPARATOR_PROP); + if (lineSeparator == null) { + lineSeparator = ReadWriteUtils.getSystemLineSeparator(); + } } } CharSequence docText = (CharSequence) getProperty(CharSequence.class); --- a/editor/nbproject/project.xml +++ a/editor/nbproject/project.xml @@ -167,7 +167,7 @@ - 7.55 + 7.56 @@ -261,6 +261,11 @@ + org.netbeans.modules.editor.mimelookup + + + + org.netbeans.modules.editor.mimelookup.impl --- a/editor/src/org/netbeans/modules/editor/EditorModule.java +++ a/editor/src/org/netbeans/modules/editor/EditorModule.java @@ -637,6 +637,10 @@ final StyledDocument doc = ec.openDocument(); final Reformat reformat = Reformat.get(doc); + String defaultLineSeparator = (String) file.getPrimaryFile().getAttribute(BaseDocument.DEFAULT_LINE_SEPARATOR_PROP); + if (defaultLineSeparator != null) { + doc.putProperty(BaseDocument.DEFAULT_LINE_SEPARATOR_PROP, defaultLineSeparator); + } reformat.lock(); @@ -658,7 +662,12 @@ } finally { reformat.unlock(); - doc.putProperty(BaseDocument.READ_LINE_SEPARATOR_PROP, ReadWriteUtils.getSystemLineSeparator()); + defaultLineSeparator = (String) doc.getProperty(BaseDocument.DEFAULT_LINE_SEPARATOR_PROP); + if (defaultLineSeparator != null) { + doc.putProperty(BaseDocument.READ_LINE_SEPARATOR_PROP, defaultLineSeparator); + } else { + doc.putProperty(BaseDocument.READ_LINE_SEPARATOR_PROP, ReadWriteUtils.getSystemLineSeparator()); + } ec.saveDocument(); } --- a/editor/test/unit/src/org/netbeans/modules/editor/LineSeparatorDataEditorSupportTest.java +++ a/editor/test/unit/src/org/netbeans/modules/editor/LineSeparatorDataEditorSupportTest.java @@ -0,0 +1,493 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.editor; + +import java.io.ByteArrayInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.logging.Level; +import java.util.prefs.Preferences; +import javax.swing.SwingUtilities; +import javax.swing.text.BadLocationException; +import javax.swing.text.StyledDocument; +import org.netbeans.api.editor.mimelookup.MimeLookup; +import org.netbeans.api.editor.mimelookup.MimePath; +import org.netbeans.api.editor.mimelookup.test.MockMimeLookup; +import org.netbeans.junit.MockServices; +import org.netbeans.junit.NbTestCase; +import org.openide.cookies.EditCookie; +import org.openide.cookies.OpenCookie; +import org.openide.filesystems.FileLock; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileStateInvalidException; +import org.openide.filesystems.FileSystem; +import org.openide.loaders.DataNode; +import org.openide.loaders.DataObject; +import org.openide.loaders.DataObjectExistsException; +import org.openide.loaders.MultiDataObject; +import org.openide.nodes.Children; +import org.openide.nodes.CookieSet; +import org.openide.nodes.Node; +import org.openide.text.DataEditorSupport; +import org.openide.util.Exceptions; +import org.openide.util.NbPreferences; + +/** + * + * @author Alexander Simon + */ +public class LineSeparatorDataEditorSupportTest extends NbTestCase { + // for file object support + + String content = ""; + long expectedSize = -1; + java.util.Date date = new java.util.Date(); + LineSeparatorDataEditorSupportTest.MyFileObject fileObject; + org.openide.filesystems.FileSystem fs; + static LineSeparatorDataEditorSupportTest RUNNING; + private MimePath textMimePath; + + static { + //System.setProperty ("org.openide.util.Lookup", "org.openide.text.LineSeparatorDataEditorSupportTest$Lkp"); + System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false"); + } + + public LineSeparatorDataEditorSupportTest(String s) { + super(s); + } + + @Override + protected Level logLevel() { + return Level.FINE; + } + + @Override + protected void setUp() throws Exception { + RUNNING = this; + MockServices.setServices(new Class[]{MockMimeLookup.class, Pool.class}); + textMimePath = MimePath.parse("text/plain"); + MockMimeLookup.setInstances(textMimePath, new NbEditorKit(), NbPreferences.forModule(getClass())); + + fs = org.openide.filesystems.FileUtil.createMemoryFileSystem(); + org.openide.filesystems.Repository.getDefault().addFileSystem(fs); + org.openide.filesystems.FileObject root = fs.getRoot(); + fileObject = new LineSeparatorDataEditorSupportTest.MyFileObject(org.openide.filesystems.FileUtil.createData(root, "my" + getName() + ".txt")); + } + + @Override + protected void tearDown() throws Exception { + waitEQ(); + + RUNNING = null; + org.openide.filesystems.Repository.getDefault().removeFileSystem(fs); + } + + @Override + protected boolean runInEQ() { + return false; + } + + private void waitEQ() throws Exception { + javax.swing.SwingUtilities.invokeAndWait(new Runnable() { + + @Override + public void run() { + } + }); + } + + LineSeparatorDataEditorSupportTest.DES support() throws Exception { + assertNotNull(MimeLookup.getLookup(textMimePath).lookup(Preferences.class)); + DataObject tmpObj = DataObject.find(fileObject); + + assertEquals("My object was created", LineSeparatorDataEditorSupportTest.MyDataObject.class, tmpObj.getClass()); + Object cookie = tmpObj.getCookie(org.openide.cookies.OpenCookie.class); + assertNotNull("Our object has this cookie", cookie); + assertEquals("It is my cookie", LineSeparatorDataEditorSupportTest.DES.class, cookie.getClass()); + + return (LineSeparatorDataEditorSupportTest.DES) cookie; + } + + public void testLineSeparator() throws Exception { + LineSeparatorDataEditorSupportTest.DES des = support(); + final StyledDocument doc = des.openDocument(); + SwingUtilities.invokeAndWait(new Runnable() { + + @Override + public void run() { + try { + doc.insertString(doc.getLength(), " Added text.\n", null); + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + } + }); + + des.saveDocument(); + assertEquals(" Added text.\r", content); + } + + /** + * File object that let us know what is happening and delegates to certain + * instance variables of the test. + */ + private static final class MyFileObject extends org.openide.filesystems.FileObject { + + private org.openide.filesystems.FileObject delegate; + private int openStreams; + private Throwable previousStream; + + public MyFileObject(org.openide.filesystems.FileObject del) { + delegate = del; + } + + @Override + public java.io.OutputStream getOutputStream(FileLock lock) throws IOException { + if (openStreams != 0) { + IOException e = new IOException("There is stream already, cannot write down!"); + if (previousStream != null) { + e.initCause(previousStream); + } + throw e; + } + class ContentStream extends java.io.ByteArrayOutputStream { + + public ContentStream() { + openStreams = -1; + } + + @Override + public void close() throws java.io.IOException { + if (openStreams != -1) { + IOException ex = new IOException("One output stream"); + ex.initCause(previousStream); + throw ex; + } + //assertEquals("One output stream", -1, openStreams); + openStreams = 0; + previousStream = new Exception("Closed"); + super.close(); + RUNNING.content = new String(toByteArray()); + } + } + previousStream = new Exception("Output"); + return new ContentStream(); + } + + @Override + public void delete(FileLock lock) throws IOException { + delegate.delete(lock); + } + + @Override + public void setImportant(boolean b) { + delegate.setImportant(b); + } + + @Override + public void addFileChangeListener(org.openide.filesystems.FileChangeListener fcl) { + delegate.addFileChangeListener(fcl); + } + + @Override + public void removeFileChangeListener(org.openide.filesystems.FileChangeListener fcl) { + delegate.removeFileChangeListener(fcl); + } + + @Override + public Object getAttribute(String attrName) { + if (attrName.equals("default-line-separator")) { + return "\r"; // NOI18N + } + return delegate.getAttribute(attrName); + } + + @Override + public FileObject createFolder(String name) throws IOException { + throw new IOException("Not supported"); + } + + @Override + public void rename(FileLock lock, String name, String ext) throws IOException { + throw new IOException("Not supported"); + } + + @Override + public void setAttribute(String attrName, Object value) throws IOException { + delegate.setAttribute(attrName, value); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public java.io.InputStream getInputStream() throws java.io.FileNotFoundException { + if (openStreams < 0) { + FileNotFoundException e = new FileNotFoundException("Already exists output stream"); + if (previousStream != null) { + e.initCause(previousStream); + } + throw e; + } + + class IS extends ByteArrayInputStream { + + public IS(byte[] arr) { + super(arr); + openStreams++; + } + + @Override + public void close() throws IOException { + openStreams--; + super.close(); + } + } + previousStream = new Exception("Input"); + + return new IS(RUNNING.content.getBytes()); + } + + @Override + public FileSystem getFileSystem() throws FileStateInvalidException { + return delegate.getFileSystem(); + } + + @Override + public FileObject getFileObject(String name, String ext) { + return null; + } + + @Override + public String getExt() { + return delegate.getExt(); + } + + @Override + public FileObject[] getChildren() { + return null; + } + + @Override + public java.util.Enumeration getAttributes() { + return delegate.getAttributes(); + } + + @Override + public FileObject createData(String name, String ext) throws IOException { + throw new IOException("Not supported"); + } + + @Override + public FileObject getParent() { + return delegate.getParent(); + } + + @Override + public long getSize() { + return RUNNING.expectedSize; + } + + @Override + public boolean isData() { + return true; + } + + @Override + public boolean isFolder() { + return false; + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public boolean isRoot() { + return false; + } + + @Override + public boolean isValid() { + return delegate.isValid(); + } + + @Override + public java.util.Date lastModified() { + return RUNNING.date; + } + + @Override + public FileLock lock() throws IOException { + return delegate.lock(); + } + + public Object writeReplace() { + return new LineSeparatorDataEditorSupportTest.Replace(); + } + } + + private static final class Replace extends Object implements java.io.Serializable { + + static final long serialVersionUID = 2L; + + public Object readResolve() { + return RUNNING.fileObject; + } + } + + /** + * Implementation of the DES + */ + private static final class DES extends DataEditorSupport + implements OpenCookie, EditCookie { + + public DES(DataObject obj, DataEditorSupport.Env env) { + super(obj, env); + } + + public org.openide.windows.CloneableTopComponent.Ref getRef() { + return allEditors; + } + } + + /** + * MyEnv that uses DataEditorSupport.Env + */ + private static final class MyEnv extends DataEditorSupport.Env { + + static final long serialVersionUID = 1L; + + public MyEnv(DataObject obj) { + super(obj); + } + + @Override + protected FileObject getFile() { + return super.getDataObject().getPrimaryFile(); + } + + @Override + public String getMimeType() { + return "text/plain"; + } + + @Override + protected FileLock takeLock() throws IOException { + return super.getDataObject().getPrimaryFile().lock(); + } + } + + public static final class Pool extends org.openide.loaders.DataLoaderPool { + + @Override + protected java.util.Enumeration loaders() { + return org.openide.util.Enumerations.singleton(LineSeparatorDataEditorSupportTest.MyLoader.get()); + } + } + + public static final class MyLoader extends org.openide.loaders.UniFileLoader { + + public int primary; + + public static LineSeparatorDataEditorSupportTest.MyLoader get() { + return LineSeparatorDataEditorSupportTest.MyLoader.findObject(LineSeparatorDataEditorSupportTest.MyLoader.class, true); + } + + public MyLoader() { + super(LineSeparatorDataEditorSupportTest.MyDataObject.class.getName()); + getExtensions().addExtension("txt"); + } + + protected String displayName() { + return "MyPart"; + } + + @Override + protected MultiDataObject createMultiObject(FileObject primaryFile) throws DataObjectExistsException, IOException { + return new LineSeparatorDataEditorSupportTest.MyDataObject(this, primaryFile); + } + + @Override + protected MultiDataObject.Entry createPrimaryEntry(MultiDataObject obj, FileObject primaryFile) { + primary++; + return new org.openide.loaders.FileEntry(obj, primaryFile); + } + } + + public static final class MyDataObject extends MultiDataObject + implements CookieSet.Factory { + + public MyDataObject(LineSeparatorDataEditorSupportTest.MyLoader l, FileObject folder) throws DataObjectExistsException { + super(folder, l); + getCookieSet().add(OpenCookie.class, this); + } + + @Override + public org.openide.nodes.Node.Cookie createCookie(Class klass) { + return new LineSeparatorDataEditorSupportTest.DES(this, new LineSeparatorDataEditorSupportTest.MyEnv(this)); + } + + @Override + protected Node createNodeDelegate() { + return new LineSeparatorDataEditorSupportTest.MyNode(this, Children.LEAF); + } + } + + /* + * Node which always returns non-null getHtmlDisplayName + */ + public static final class MyNode extends DataNode { + + public MyNode(DataObject obj, Children ch) { + super(obj, ch); + } + + @Override + public String getHtmlDisplayName() { + return "" + getDisplayName() + ""; + } + } +} --- a/openide.filesystems/apichanges.xml +++ a/openide.filesystems/apichanges.xml @@ -49,6 +49,24 @@ Filesystems API + + + Provides default line separator + + + + + +

+ File object can provide default line separator if it differs from System.getProperty("line.separator"). + Call fo.getAttribute("default-line-separator") return string with default line separator. + Default line separator will be used by the text editor if saving new content to an initially empty file. + Any other code which creates file content programmatically must manually read this property if it cares. +

+
+ + +
Introduced FileObject.revert --- a/openide.filesystems/manifest.mf +++ a/openide.filesystems/manifest.mf @@ -2,5 +2,5 @@ OpenIDE-Module: org.openide.filesystems OpenIDE-Module-Localizing-Bundle: org/openide/filesystems/Bundle.properties OpenIDE-Module-Layer: org/openide/filesystems/resources/layer.xml -OpenIDE-Module-Specification-Version: 7.55 +OpenIDE-Module-Specification-Version: 7.56 --- a/openide.loaders/manifest.mf +++ a/openide.loaders/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.openide.loaders -OpenIDE-Module-Specification-Version: 7.34 +OpenIDE-Module-Specification-Version: 7.35 OpenIDE-Module-Localizing-Bundle: org/openide/loaders/Bundle.properties OpenIDE-Module-Provides: org.netbeans.modules.templates.v1_0 OpenIDE-Module-Layer: org/netbeans/modules/openide/loaders/layer.xml --- a/openide.loaders/nbproject/project.xml +++ a/openide.loaders/nbproject/project.xml @@ -122,7 +122,7 @@ - 7.55 + 7.56
--- a/openide.loaders/src/org/openide/text/DataEditorSupport.java +++ a/openide.loaders/src/org/openide/text/DataEditorSupport.java @@ -125,6 +125,13 @@ /** error manager for CloneableEditorSupport logging and error reporting */ static final Logger ERR = Logger.getLogger("org.openide.text.DataEditorSupport"); // NOI18N + /** Default line separator property for writing content into files. + * It will be used by the text editor if saving new content to an initially empty file. + * Any other code which creates file content programmatically must manually read this property if it cares. + * If it not set the platform default line separator is used. + */ + private static final String DEFAULT_LINE_SEPARATOR_PROP = "default-line-separator"; // NOI18N + /** Which data object we are associated with */ private final DataObject obj; /** listener to associated node's events */ @@ -480,6 +487,7 @@ c = FileEncodingQuery.getEncoding(this.getDataObject().getPrimaryFile()); } final FileObject fo = this.getDataObject().getPrimaryFile(); + doc.putProperty(DEFAULT_LINE_SEPARATOR_PROP, fo.getAttribute(DEFAULT_LINE_SEPARATOR_PROP)); final Reader r; if (warnedEncodingFiles.contains(fo)) { r = new InputStreamReader (stream, c);