This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 217904
Collapse All | Expand All

(-)a/editor.lib/src/org/netbeans/editor/BaseDocument.java (-1 lines)
Lines 574-580 Link Here
574
        this.addUpdateDocumentListener(modElementRoot);
574
        this.addUpdateDocumentListener(modElementRoot);
575
        modElementRoot.setEnabled(true);
575
        modElementRoot.setEnabled(true);
576
576
577
        TrailingWhitespaceRemove.ensureRegistered();
578
        BeforeSaveTasks.get(this); // Ensure that "beforeSaveRunnable" gets initialized
577
        BeforeSaveTasks.get(this); // Ensure that "beforeSaveRunnable" gets initialized
579
578
580
        undoEditWrappers = MimeLookup.getLookup(mimeType).lookupAll(UndoableEditWrapper.class);
579
        undoEditWrappers = MimeLookup.getLookup(mimeType).lookupAll(UndoableEditWrapper.class);
(-)a/editor.lib/src/org/netbeans/modules/editor/lib/BeforeSaveTasks.java (-121 / +71 lines)
Lines 45-56 Link Here
45
package org.netbeans.modules.editor.lib;
45
package org.netbeans.modules.editor.lib;
46
46
47
import java.util.ArrayList;
47
import java.util.ArrayList;
48
import java.util.Collection;
48
import java.util.List;
49
import java.util.List;
49
import java.util.logging.Level;
50
import java.util.logging.Level;
50
import java.util.logging.Logger;
51
import java.util.logging.Logger;
51
import javax.swing.text.Document;
52
import javax.swing.undo.UndoableEdit;
52
import javax.swing.undo.UndoableEdit;
53
import org.netbeans.api.editor.mimelookup.MimeLookup;
53
import org.netbeans.editor.BaseDocument;
54
import org.netbeans.editor.BaseDocument;
55
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
56
import org.netbeans.modules.editor.lib2.document.DocumentSpiPackageAccessor;
57
import org.netbeans.modules.editor.lib2.document.ModRootElement;
58
import org.netbeans.spi.editor.document.OnSaveTask;
54
59
55
/**
60
/**
56
 * Registration of tasks performed right before document save.
61
 * Registration of tasks performed right before document save.
Lines 62-69 Link Here
62
    
67
    
63
    private static final Logger LOG = Logger.getLogger(BeforeSaveTasks.class.getName());
68
    private static final Logger LOG = Logger.getLogger(BeforeSaveTasks.class.getName());
64
69
65
    private static final List<Task> tasks = new ArrayList<Task>(5);
66
    
67
    public static synchronized BeforeSaveTasks get(BaseDocument doc) {
70
    public static synchronized BeforeSaveTasks get(BaseDocument doc) {
68
        BeforeSaveTasks beforeSaveTasks = (BeforeSaveTasks) doc.getProperty(BeforeSaveTasks.class);
71
        BeforeSaveTasks beforeSaveTasks = (BeforeSaveTasks) doc.getProperty(BeforeSaveTasks.class);
69
        if (beforeSaveTasks == null) {
72
        if (beforeSaveTasks == null) {
Lines 91-220 Link Here
91
        doc.putProperty("beforeSaveRunnable", beforeSaveRunnable); // NOI18N
94
        doc.putProperty("beforeSaveRunnable", beforeSaveRunnable); // NOI18N
92
    }
95
    }
93
96
94
    /**
97
    void runTasks() {
95
     * Add a new task to be executed before save of the document.
98
        String mimeType = DocumentUtilities.getMimeType(doc);
96
     *
99
        Collection<? extends OnSaveTask.Factory> factories = MimeLookup.getLookup(mimeType).
97
     * @param task non-null task.
100
                lookupAll(OnSaveTask.Factory.class);
98
     */
101
        OnSaveTask.Context context = DocumentSpiPackageAccessor.get().createContext(doc);
99
    public static void addTask(Task task) {
102
        List<OnSaveTask> tasks = new ArrayList<OnSaveTask>(factories.size());
100
        if (task == null)
103
        for (OnSaveTask.Factory factory : factories) {
101
            throw new IllegalArgumentException("task must not be null"); // NOI18N
104
            OnSaveTask task = factory.createTask(context);
102
        synchronized (tasks) {
105
            if (task != null) {
103
            tasks.add(task);
106
                tasks.add(task);
107
            }
108
        }
109
        if (tasks.size() > 0) {
110
            new TaskRunnable(doc, tasks, context).run();
104
        }
111
        }
105
    }
112
    }
106
113
107
    /**
114
    private static final class TaskRunnable implements Runnable {
108
     * Remove a task from the list of existing before-save tasks.
115
        
109
     *
116
        final BaseDocument doc;
110
     * @param task runnable to be removed.
117
111
     * @return true if the tasks was removed successfully or false if the task
118
        final List<OnSaveTask> tasks;
112
     *  was not found (compared by <code>Object.equals()</code>).
119
        
113
     */
120
        final OnSaveTask.Context context;
114
    public static boolean removeTask(Task task) {
121
115
        synchronized (tasks) {
122
        int lockedTaskIndex;
116
            return tasks.remove(task);
123
124
        public TaskRunnable(BaseDocument doc, List<OnSaveTask> tasks, OnSaveTask.Context context) {
125
            this.doc = doc;
126
            this.tasks = tasks;
127
            this.context = context;
117
        }
128
        }
118
    }
129
119
    
130
        @Override
120
    public static boolean removeTask(Class taskClass) {
131
        public void run() {
121
        synchronized (tasks) {
132
            if (lockedTaskIndex < tasks.size()) {
122
            int i = taskIndex(taskClass);
133
                OnSaveTask task = tasks.get(lockedTaskIndex++);
123
            if (i >= 0) {
134
                task.runLocked(this);
124
                tasks.remove(i);
135
125
                return true;
136
            } else {
137
                try {
138
                    doc.runAtomicAsUser(new Runnable() {
139
                        public @Override
140
                        void run() {
141
                            UndoableEdit atomicEdit = EditorPackageAccessor.get().BaseDocument_markAtomicEditsNonSignificant(doc);
142
                            DocumentSpiPackageAccessor.get().setUndoEdit(context, atomicEdit);
143
144
                            // Since these are before-save actions they should generally not prevent
145
                            // the save operation to succeed. Thus the possible exceptions thrown
146
                            // by the tasks will be notified but they will not prevent the save to succeed.
147
                            try {
148
                                for (int i = 0; i < tasks.size(); i++) {
149
                                    OnSaveTask task = tasks.get(i);
150
                                    DocumentSpiPackageAccessor.get().setTaskStarted(context, true);
151
                                    task.performTask();
152
                                }
153
                                ModRootElement modRootElement = ModRootElement.get(doc);
154
                                if (modRootElement != null) {
155
                                    modRootElement.resetMods(atomicEdit);
156
                                }
157
                            } catch (Exception e) {
158
                                LOG.log(Level.WARNING, "Exception thrown in before-save tasks", e); // NOI18N
159
                            }
160
161
                        }
162
                    });
163
                } finally {
164
                    EditorPackageAccessor.get().BaseDocument_clearAtomicEdits(doc);
165
                }
126
            }
166
            }
127
        }
167
        }
128
        return false;
129
    }
130
131
    public static Task getTask(Class taskClass) {
132
        synchronized (tasks) {
133
            int i = taskIndex(taskClass);
134
            return (i >= 0) ? tasks.get(i) : null;
135
        }
136
    }
137
138
    private static int taskIndex(Class taskClass) {
139
        for (int i = tasks.size() - 1; i >= 0; i--) {
140
            Task task = tasks.get(i);
141
            if (taskClass == task.getClass()) {
142
                return i;
143
            }
144
        }
145
        return -1;
146
    }
147
148
    void runTasks() {
149
        final List<Task> tasksCopy = new ArrayList<Task>(tasks);
150
        final List<Object> locks = new ArrayList<Object>(tasksCopy.size());
151
        int taskCount = tasksCopy.size();
152
        int lockedTaskEndIndex = 0;
153
        for (;lockedTaskEndIndex < taskCount; lockedTaskEndIndex++) {
154
            Task task = tasksCopy.get(lockedTaskEndIndex);
155
            locks.add(task.lock(doc));
156
        }
157
        try {
158
            doc.runAtomicAsUser (new Runnable () {
159
                public @Override void run () {
160
                    UndoableEdit atomicEdit = EditorPackageAccessor.get().BaseDocument_markAtomicEditsNonSignificant(doc);
161
                    // Since these are before-save actions they should generally not prevent
162
                    // the save operation to succeed. Thus the possible exceptions thrown
163
                    // by the tasks will be notified but they will not prevent the save to succeed.
164
                    try {
165
                        for (int i = 0; i < tasksCopy.size(); i++) {
166
                            Task task = tasksCopy.get(i);
167
                            task.run(locks.get(i), doc, atomicEdit);
168
                        }
169
                    } catch (Exception e) {
170
                        LOG.log(Level.WARNING, "Exception thrown in before-save tasks", e); // NOI18N
171
                    }
172
173
                }
174
            });
175
        } finally {
176
            while (lockedTaskEndIndex > 0) {
177
                Task task = tasksCopy.get(--lockedTaskEndIndex);
178
                task.unlock(locks.get(lockedTaskEndIndex), doc);
179
            }
180
181
            EditorPackageAccessor.get().BaseDocument_clearAtomicEdits(doc);
182
        }
183
    }
184
185
    /**
186
     * Task run right before document saving such as reformatting or trailing whitespace removal.
187
     */
188
    public interface Task {
189
190
        /**
191
         * Perform an extra lock for task if necessary.
192
         * Locks for all tasks will be obtained first then atomic document lock will be obtained
193
         * and then all the tasks will be run. Then all the locks will be released by running unlock()
194
         * in all tasks in a reverse order of locking.
195
         * @param doc non-null document.
196
         */
197
        Object lock(Document doc);
198
199
        /**
200
         * Run the before-save task.
201
         *
202
         * @param lockInfo lock object produced by {@link #lock(javax.swing.text.Document) }
203
         *  that may contain an arbitrary info.
204
         * @param doc non-null document.
205
         * @param edit a non-ended edit to which undoable edits of the task should be added.
206
         *  It may be null in which case the produced tasks should not be added to anything.
207
         */
208
        void run(Object lockInfo, Document doc, UndoableEdit edit);
209
210
        /**
211
         * Perform an extra unlock for task if necessary.
212
         *
213
         * @param lockInfo lock object produced by {@link #lock(javax.swing.text.Document) }
214
         *  that may contain an arbitrary info.
215
         * @param doc non-null document.
216
         */
217
        void unlock(Object lockInfo, Document doc);
218
168
219
    }
169
    }
220
170
(-)a/editor.lib/src/org/netbeans/modules/editor/lib/TrailingWhitespaceRemove.java (-17 / +29 lines)
Lines 44-59 Link Here
44
44
45
package org.netbeans.modules.editor.lib;
45
package org.netbeans.modules.editor.lib;
46
46
47
import java.util.concurrent.atomic.AtomicBoolean;
47
import java.util.logging.Logger;
48
import java.util.logging.Logger;
48
import java.util.prefs.Preferences;
49
import java.util.prefs.Preferences;
49
import javax.swing.text.Document;
50
import javax.swing.text.Document;
50
import javax.swing.undo.UndoableEdit;
51
import javax.swing.undo.UndoableEdit;
51
import org.netbeans.api.editor.mimelookup.MimeLookup;
52
import org.netbeans.api.editor.mimelookup.MimeLookup;
52
import org.netbeans.api.editor.mimelookup.MimePath;
53
import org.netbeans.api.editor.mimelookup.MimePath;
54
import org.netbeans.api.editor.mimelookup.MimeRegistration;
53
import org.netbeans.api.editor.settings.SimpleValueNames;
55
import org.netbeans.api.editor.settings.SimpleValueNames;
54
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
56
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
55
import org.netbeans.modules.editor.lib2.document.ModRootElement;
57
import org.netbeans.modules.editor.lib2.document.ModRootElement;
56
import org.netbeans.modules.editor.lib2.document.TrailingWhitespaceRemoveProcessor;
58
import org.netbeans.modules.editor.lib2.document.TrailingWhitespaceRemoveProcessor;
59
import org.netbeans.spi.editor.document.OnSaveTask;
57
60
58
/**
61
/**
59
 * Removal of trailing whitespace
62
 * Removal of trailing whitespace
Lines 61-89 Link Here
61
 * @author Miloslav Metelka
64
 * @author Miloslav Metelka
62
 * @since 1.27
65
 * @since 1.27
63
 */
66
 */
64
public final class TrailingWhitespaceRemove implements BeforeSaveTasks.Task {
67
public final class TrailingWhitespaceRemove implements OnSaveTask {
65
68
66
    // -J-Dorg.netbeans.modules.editor.lib.TrailingWhitespaceRemove.level=FINE
69
    // -J-Dorg.netbeans.modules.editor.lib.TrailingWhitespaceRemove.level=FINE
67
    static final Logger LOG = Logger.getLogger(TrailingWhitespaceRemove.class.getName());
70
    static final Logger LOG = Logger.getLogger(TrailingWhitespaceRemove.class.getName());
68
71
69
    private static final TrailingWhitespaceRemove INSTANCE = new TrailingWhitespaceRemove();
72
    private final Document doc;
73
    
74
    private AtomicBoolean canceled = new AtomicBoolean();
70
75
71
    public static void ensureRegistered() {
76
    TrailingWhitespaceRemove(Document doc) {
72
        if (BeforeSaveTasks.getTask(TrailingWhitespaceRemove.class) == null) {
77
        this.doc = doc;
73
            BeforeSaveTasks.addTask(INSTANCE);
74
        }
75
    }
76
77
    private TrailingWhitespaceRemove() {
78
    }
78
    }
79
79
80
    @Override
80
    @Override
81
    public Object lock(Document doc) {
81
    public void performTask() {
82
        return null; // No extra locking
83
    }
84
85
    @Override
86
    public void run(Object lock, Document doc, UndoableEdit compoundEdit) {
87
        Preferences prefs = MimeLookup.getLookup(DocumentUtilities.getMimeType(doc)).lookup(Preferences.class);
82
        Preferences prefs = MimeLookup.getLookup(DocumentUtilities.getMimeType(doc)).lookup(Preferences.class);
88
        if (prefs.getBoolean(SimpleValueNames.ON_SAVE_USE_GLOBAL_SETTINGS, Boolean.TRUE)) {
83
        if (prefs.getBoolean(SimpleValueNames.ON_SAVE_USE_GLOBAL_SETTINGS, Boolean.TRUE)) {
89
            prefs = MimeLookup.getLookup(MimePath.EMPTY).lookup(Preferences.class);
84
            prefs = MimeLookup.getLookup(MimePath.EMPTY).lookup(Preferences.class);
Lines 95-101 Link Here
95
                boolean origEnabled = modRootElement.isEnabled();
90
                boolean origEnabled = modRootElement.isEnabled();
96
                modRootElement.setEnabled(false);
91
                modRootElement.setEnabled(false);
97
                try {
92
                try {
98
                    new TrailingWhitespaceRemoveProcessor(doc, "modified-lines".equals(policy)).removeWhitespace(); //NOI18N
93
                    new TrailingWhitespaceRemoveProcessor(doc, "modified-lines".equals(policy), canceled).removeWhitespace(); //NOI18N
99
                } finally {
94
                } finally {
100
                    modRootElement.setEnabled(origEnabled);
95
                    modRootElement.setEnabled(origEnabled);
101
                }
96
                }
Lines 104-110 Link Here
104
    }
99
    }
105
100
106
    @Override
101
    @Override
107
    public void unlock(Object lock, Document doc) {
102
    public void runLocked(Runnable run) {
103
        run.run();
104
    }
105
106
    @Override
107
    public boolean cancel() {
108
        canceled.set(true);
109
        return true;
110
    }
111
112
    @MimeRegistration(mimeType="", service=OnSaveTask.Factory.class, position=1000)
113
    public static final class FactoryImpl implements Factory {
114
115
        @Override
116
        public OnSaveTask createTask(Context context) {
117
            return new TrailingWhitespaceRemove(context.getDocument());
118
        }
119
108
    }
120
    }
109
121
110
}
122
}
(-)a/editor.lib/test/unit/src/org/netbeans/modules/editor/lib/BeforeSaveTasksTest.java (+169 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2012 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2012 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.editor.lib;
43
44
import javax.swing.undo.UndoableEdit;
45
import org.netbeans.api.editor.mimelookup.MimePath;
46
import org.netbeans.api.editor.mimelookup.test.MockMimeLookup;
47
import org.netbeans.editor.BaseDocument;
48
import org.netbeans.junit.MockServices;
49
import org.netbeans.junit.NbTestCase;
50
import org.netbeans.spi.editor.document.OnSaveTask;
51
52
/**
53
 *
54
 * @author Miloslav Metelka
55
 */
56
public class BeforeSaveTasksTest extends NbTestCase {
57
58
    private static final String MIME_TYPE = "text/x-test-on-save";
59
60
    public BeforeSaveTasksTest(String name) {
61
        super(name);
62
    }
63
64
    public void testOnSaveTasks() {
65
        MockServices.setServices(MockMimeLookup.class);
66
        MockMimeLookup.setInstances(MimePath.parse(MIME_TYPE),
67
                new TestOnSaveTask1.TestFactory1(),
68
                new TestOnSaveTask2.TestFactory2()
69
        );
70
        BaseDocument doc = new BaseDocument(false, MIME_TYPE);
71
        BeforeSaveTasks.get(doc);
72
        Runnable beforeSaveRunnable = (Runnable) doc.getProperty("beforeSaveRunnable");
73
        beforeSaveRunnable.run();
74
        assertNotNull("TestOnSaveTask2 not created", TestOnSaveTask2.TestFactory2.lastCreatedTask);
75
        assertTrue("TestOnSaveTask2 not run", TestOnSaveTask2.TestFactory2.lastCreatedTask.taskPerformed);
76
    }
77
78
    private static final class TestOnSaveTask1 implements OnSaveTask {
79
80
        boolean taskLocked;
81
82
        boolean taskPerformed;
83
84
        TestOnSaveTask1(Context context) {
85
        }
86
87
        @Override
88
        public void performTask() {
89
            assertTrue("Task not locked", taskLocked);
90
            assertFalse("Task run multiple times", taskPerformed);
91
            taskPerformed = true;
92
        }
93
94
        @Override
95
        public void runLocked(Runnable run) {
96
            taskLocked = true;
97
            try {
98
                run.run();
99
            } finally {
100
                taskLocked = false;
101
            }
102
        }
103
104
        @Override
105
        public boolean cancel() {
106
            return true;
107
        }
108
        
109
        static final class TestFactory1 implements OnSaveTask.Factory {
110
111
            static TestOnSaveTask1 lastCreatedTask;
112
113
            public OnSaveTask createTask(Context context) {
114
                assertNotNull("Context null", context);
115
                return (lastCreatedTask = new TestOnSaveTask1(context));
116
            }
117
118
        }
119
120
    }
121
122
    private static final class TestOnSaveTask2 implements OnSaveTask {
123
124
        boolean taskLocked;
125
126
        boolean taskPerformed;
127
128
        TestOnSaveTask2(OnSaveTask.Context context) {
129
        }
130
131
        @Override
132
        public void performTask() {
133
            assertTrue("Task1 not locked", TestOnSaveTask1.TestFactory1.lastCreatedTask.taskLocked);
134
            assertTrue("Task1 not performed yet", TestOnSaveTask1.TestFactory1.lastCreatedTask.taskPerformed);
135
136
            assertTrue("Task not locked", taskLocked);
137
            assertFalse("Task run multiple times", taskPerformed);
138
            taskPerformed = true;
139
        }
140
141
        @Override
142
        public void runLocked(Runnable run) {
143
            taskLocked = true;
144
            try {
145
                run.run();
146
            } finally {
147
                taskLocked = false;
148
            }
149
        }
150
151
        @Override
152
        public boolean cancel() {
153
            return true;
154
        }
155
        
156
        static final class TestFactory2 implements OnSaveTask.Factory {
157
158
            static TestOnSaveTask2 lastCreatedTask;
159
160
            public OnSaveTask createTask(OnSaveTask.Context context) {
161
                assertNotNull("Context null", context);
162
                return (lastCreatedTask = new TestOnSaveTask2(context));
163
            }
164
165
        }
166
167
    }
168
169
}
(-)a/editor.lib/test/unit/src/org/netbeans/modules/editor/lib/TrailingWhitespaceRemoveTest.java (+7 lines)
Lines 52-60 Link Here
52
import javax.swing.undo.UndoManager;
52
import javax.swing.undo.UndoManager;
53
import org.netbeans.api.editor.mimelookup.MimeLookup;
53
import org.netbeans.api.editor.mimelookup.MimeLookup;
54
import org.netbeans.api.editor.mimelookup.MimePath;
54
import org.netbeans.api.editor.mimelookup.MimePath;
55
import org.netbeans.api.editor.mimelookup.test.MockMimeLookup;
55
import org.netbeans.api.editor.settings.SimpleValueNames;
56
import org.netbeans.api.editor.settings.SimpleValueNames;
56
import org.netbeans.editor.BaseDocument;
57
import org.netbeans.editor.BaseDocument;
57
import org.netbeans.editor.BaseKit;
58
import org.netbeans.editor.BaseKit;
59
import org.netbeans.junit.MockServices;
58
import org.netbeans.junit.NbTestCase;
60
import org.netbeans.junit.NbTestCase;
59
import org.netbeans.lib.editor.util.ArrayUtilities;
61
import org.netbeans.lib.editor.util.ArrayUtilities;
60
import org.netbeans.lib.editor.util.CharSequenceUtilities;
62
import org.netbeans.lib.editor.util.CharSequenceUtilities;
Lines 94-99 Link Here
94
    }
96
    }
95
97
96
    private void checkTrailingWhitespaceRemove(String policy, String result) throws Exception {
98
    private void checkTrailingWhitespaceRemove(String policy, String result) throws Exception {
99
        MockServices.setServices(MockMimeLookup.class);
100
        MockMimeLookup.setInstances(MimePath.parse(""),
101
                new TrailingWhitespaceRemove.FactoryImpl()
102
        );
103
        
97
        RandomTestContainer container = DocumentTesting.initContainer(null);
104
        RandomTestContainer container = DocumentTesting.initContainer(null);
98
        container.setName(this.getName());
105
        container.setName(this.getName());
99
//        container.putProperty(RandomTestContainer.LOG_OP, Boolean.TRUE);
106
//        container.putProperty(RandomTestContainer.LOG_OP, Boolean.TRUE);
(-)a/editor.lib2/apichanges.xml (+15 lines)
Lines 107-112 Link Here
107
    <!-- ACTUAL CHANGES BEGIN HERE: -->
107
    <!-- ACTUAL CHANGES BEGIN HERE: -->
108
108
109
    <changes>
109
    <changes>
110
        <change id="OnSaveTask">
111
            <summary>OnSaveTask interface added</summary>
112
            <version major="1" minor="66"/>
113
            <date day="5" month="9" year="2012"/>
114
            <author login="mmetelka"/>
115
            <compatibility binary="compatible" source="compatible" semantic="compatible" addition="yes"/>
116
            <description>
117
                <p>
118
                    Added OnSaveTask interface which allows modules to register
119
                    tasks into MimeLookup that will be performed right before document saving.
120
                </p>
121
            </description>
122
            <issue number="217904"/>
123
        </change>
124
110
        <change id="UndoableEditWrapper">
125
        <change id="UndoableEditWrapper">
111
            <summary>UndoableEditWrapper interface added</summary>
126
            <summary>UndoableEditWrapper interface added</summary>
112
            <version major="1" minor="60"/>
127
            <version major="1" minor="60"/>
(-)a/editor.lib2/manifest.mf (-1 / +1 lines)
Lines 1-6 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.netbeans.modules.editor.lib2/1
2
OpenIDE-Module: org.netbeans.modules.editor.lib2/1
3
OpenIDE-Module-Implementation-Version: 33
3
OpenIDE-Module-Implementation-Version: 34
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/lib2/Bundle.properties
4
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/lib2/Bundle.properties
5
OpenIDE-Module-Layer: org/netbeans/modules/editor/lib2/resources/layer.xml
5
OpenIDE-Module-Layer: org/netbeans/modules/editor/lib2/resources/layer.xml
6
OpenIDE-Module-Needs: org.netbeans.modules.editor.actions
6
OpenIDE-Module-Needs: org.netbeans.modules.editor.actions
(-)a/editor.lib2/nbproject/project.properties (-1 / +1 lines)
Lines 43-49 Link Here
43
is.autoload=true
43
is.autoload=true
44
javac.source=1.6
44
javac.source=1.6
45
javac.compilerargs=-Xlint:unchecked
45
javac.compilerargs=-Xlint:unchecked
46
spec.version.base=1.65.0
46
spec.version.base=1.66.0
47
47
48
javadoc.arch=${basedir}/arch.xml
48
javadoc.arch=${basedir}/arch.xml
49
javadoc.apichanges=${basedir}/apichanges.xml
49
javadoc.apichanges=${basedir}/apichanges.xml
(-)a/editor.lib2/nbproject/project.xml (+6 lines)
Lines 148-153 Link Here
148
                        <compile-dependency/>
148
                        <compile-dependency/>
149
                    </test-dependency>
149
                    </test-dependency>
150
                    <test-dependency>
150
                    <test-dependency>
151
                        <code-name-base>org.netbeans.modules.editor.mimelookup</code-name-base>
152
                        <recursive/>
153
                        <compile-dependency/>
154
                        <test/>
155
                    </test-dependency>
156
                    <test-dependency>
151
                        <code-name-base>org.netbeans.modules.editor.mimelookup.impl</code-name-base>
157
                        <code-name-base>org.netbeans.modules.editor.mimelookup.impl</code-name-base>
152
                        <recursive/>
158
                        <recursive/>
153
                    </test-dependency>
159
                    </test-dependency>
(-)a/editor.lib2/src/org/netbeans/modules/editor/lib2/document/DocumentInternalUtils.java (-5 / +23 lines)
Lines 56-69 Link Here
56
    }
56
    }
57
57
58
    public static Element customElement(Document doc, int startOffset, int endOffset) {
58
    public static Element customElement(Document doc, int startOffset, int endOffset) {
59
        return new CustomElement(doc, startOffset, endOffset);
59
        return new CustomRootElement(doc, startOffset, endOffset);
60
    }
60
    }
61
61
62
    private static final class CustomElement extends AbstractPositionElement {
62
    private static final class CustomElement extends AbstractPositionElement {
63
63
64
        CustomElement(Document doc, int startOffset, int endOffset) {
64
        CustomElement(Element parent, int startOffset, int endOffset) {
65
            super(new CustomRootElement(doc), startOffset, endOffset);
65
            super(parent, startOffset, endOffset);
66
            CharSequenceUtilities.checkIndexesValid(startOffset, endOffset, doc.getLength() + 1);
66
            CharSequenceUtilities.checkIndexesValid(startOffset, endOffset,
67
                    parent.getDocument().getLength() + 1);
67
        }
68
        }
68
69
69
        @Override
70
        @Override
Lines 76-83 Link Here
76
77
77
    private static final class CustomRootElement extends AbstractRootElement<CustomElement> {
78
    private static final class CustomRootElement extends AbstractRootElement<CustomElement> {
78
79
79
        public CustomRootElement(Document doc) {
80
        private final CustomElement customElement;
81
82
        public CustomRootElement(Document doc, int startOffset, int endOffset) {
80
            super(doc);
83
            super(doc);
84
            customElement = new CustomElement(this, startOffset, endOffset);
81
        }
85
        }
82
86
83
        @Override
87
        @Override
Lines 85-89 Link Here
85
            return "CustomRootElement";
89
            return "CustomRootElement";
86
        }
90
        }
87
91
92
        @Override
93
        public Element getElement(int index) {
94
            if (index == 0) {
95
                return customElement;
96
            } else {
97
                return null;
98
            }
99
        }
100
101
        @Override
102
        public int getElementCount() {
103
            return 1;
104
        }
105
88
    }
106
    }
89
}
107
}
(-)a/editor.lib2/src/org/netbeans/modules/editor/lib2/document/DocumentSpiPackageAccessor.java (+81 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2012 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2012 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.editor.lib2.document;
43
44
import javax.swing.text.Document;
45
import javax.swing.undo.UndoableEdit;
46
import org.netbeans.spi.editor.document.OnSaveTask;
47
import org.openide.util.Exceptions;
48
49
/**
50
 * Package accessor for o.n.spi.editor.document package.
51
 *
52
 * @author Miloslav Metelka
53
 */
54
public abstract class DocumentSpiPackageAccessor {
55
56
    private static DocumentSpiPackageAccessor INSTANCE;
57
58
    public static DocumentSpiPackageAccessor get() {
59
        if (INSTANCE == null) {
60
            // Cause api accessor impl to get initialized
61
            try {
62
                Class.forName(OnSaveTask.Context.class.getName(), true, DocumentSpiPackageAccessor.class.getClassLoader());
63
            } catch (ClassNotFoundException e) {
64
                Exceptions.printStackTrace(e);
65
            }
66
            assert (INSTANCE != null) : "Registration failed"; // NOI18N
67
        }
68
        return INSTANCE;
69
    }
70
71
    public static void register(DocumentSpiPackageAccessor accessor) {
72
        INSTANCE = accessor;
73
    }
74
75
    public abstract OnSaveTask.Context createContext(Document doc);
76
77
    public abstract void setUndoEdit(OnSaveTask.Context context, UndoableEdit undoEdit);
78
        
79
    public abstract void setTaskStarted(OnSaveTask.Context context, boolean taskStarted);
80
81
}
(-)a/editor.lib2/src/org/netbeans/modules/editor/lib2/document/TrailingWhitespaceRemoveProcessor.java (-1 / +5 lines)
Lines 41-46 Link Here
41
 */
41
 */
42
package org.netbeans.modules.editor.lib2.document;
42
package org.netbeans.modules.editor.lib2.document;
43
43
44
import java.util.concurrent.atomic.AtomicBoolean;
44
import java.util.logging.Level;
45
import java.util.logging.Level;
45
import java.util.logging.Logger;
46
import java.util.logging.Logger;
46
import javax.swing.text.BadLocationException;
47
import javax.swing.text.BadLocationException;
Lines 140-149 Link Here
140
     * Shift offset of the caret relative to caretLineIndex's line beginning.
141
     * Shift offset of the caret relative to caretLineIndex's line beginning.
141
     */
142
     */
142
    private final int caretRelativeOffset;
143
    private final int caretRelativeOffset;
144
    
145
    private final AtomicBoolean canceled;
143
146
144
    public TrailingWhitespaceRemoveProcessor(Document doc, boolean removeFromModifiedLinesOnly) {
147
    public TrailingWhitespaceRemoveProcessor(Document doc, boolean removeFromModifiedLinesOnly, AtomicBoolean canceled) {
145
        this.doc = doc;
148
        this.doc = doc;
146
        this.removeFromModifiedLinesOnly = removeFromModifiedLinesOnly;
149
        this.removeFromModifiedLinesOnly = removeFromModifiedLinesOnly;
150
        this.canceled = canceled;
147
        this.docText = DocumentUtilities.getText(doc); // Persists for doc's lifetime
151
        this.docText = DocumentUtilities.getText(doc); // Persists for doc's lifetime
148
        lineRootElement = DocumentUtilities.getParagraphRootElement(doc);
152
        lineRootElement = DocumentUtilities.getParagraphRootElement(doc);
149
        modRootElement = ModRootElement.get(doc);
153
        modRootElement = ModRootElement.get(doc);
(-)a/editor.lib2/src/org/netbeans/spi/editor/document/OnSaveTask.java (+198 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2012 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2012 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.spi.editor.document;
43
44
import javax.swing.text.Document;
45
import javax.swing.text.Element;
46
import javax.swing.undo.UndoableEdit;
47
import org.netbeans.api.annotations.common.NonNull;
48
import org.netbeans.modules.editor.lib2.document.DocumentSpiPackageAccessor;
49
import org.netbeans.modules.editor.lib2.document.ModRootElement;
50
import org.netbeans.spi.editor.mimelookup.MimeLocation;
51
import org.openide.util.Cancellable;
52
53
/**
54
 * Task done right before document is saved.
55
 * Factories need to be registered in MimeLookup.
56
 *
57
 * @author Miloslav Metelka
58
 * @since 1.66
59
 */
60
public interface OnSaveTask extends Cancellable {
61
62
    /**
63
     * Perform the task on the context (given to factory that produced this task).
64
     */
65
    void performTask();
66
67
    /**
68
     * Perform the given runnable under a lock specific for this task.
69
     * The runnable will include a call to {@link #performTask() } but it may
70
     * also call other tasks if there are multiple ones.
71
     * <br/>
72
     * For multiple task factories registered their {@link #runLocked(java.lang.Runnable) }
73
     * methods will be called subsequently (according to their registration order) in a nested way.
74
     *
75
     * @param run non-null runnable provided by the infrastructure.
76
     */
77
    void runLocked(@NonNull Runnable run);
78
79
    /**
80
     * Cancel processing of the job. Called not more than once for specific job.
81
     *
82
     * @return true if the job was successfully canceled, false if job
83
     *         can't be canceled for some reason.
84
     * @see Cancellable
85
     */
86
    public boolean cancel();
87
88
    /**
89
     * Factory for creation of on-save task.
90
     * It should be registered in MimeLookup via xml layer in "/Editors/&lt;mime-type&gt;"
91
     * folder.
92
     */
93
    @MimeLocation(subfolderName="OnSave")
94
    public interface Factory {
95
96
        /**
97
         * Create on-save task.
98
         *
99
         * @param context non-null context containing info for the task.
100
         * @return task instance or null if the task is not appropriate for the given context.
101
         */
102
        OnSaveTask createTask(@NonNull Context context);
103
104
    }
105
106
    /**
107
     * Context given to factory for production of on-save task.
108
     */
109
    public static final class Context {
110
111
        static {
112
            DocumentSpiPackageAccessor.register(new PackageAccessor());
113
        }
114
115
        private final Document doc;
116
        
117
        private UndoableEdit undoEdit;
118
        
119
        private boolean taskStarted;
120
121
        Context(Document doc) {
122
            this.doc = doc;
123
        }
124
125
        /**
126
         * Get a document on which the task is being executed.
127
         * @return 
128
         */
129
        public Document getDocument() {
130
            return doc;
131
        }
132
133
134
        /**
135
         * Task may add a custom undoable edit related to its operation by using this method.
136
         * <br/>
137
         * When undo would be performed after the save then this edit would be undone
138
         * (together with any possible modification changes performed by the task on the underlying document).
139
         * <br/>
140
         * Note: this method should only be called during {@link OnSaveTask#performTask() }.
141
         * 
142
         * @param edit a custom undoable edit provided by the task.
143
         */
144
        public void addUndoEdit(UndoableEdit edit) {
145
            if (!taskStarted) {
146
                throw new IllegalStateException("This method may only be called during OnSaveTask.performTask()");
147
            }
148
            if (undoEdit != null) {
149
                undoEdit.addEdit(edit);
150
            }
151
        }
152
        
153
        /**
154
         * Get a root element with zero or more child elements each designating a modified region
155
         * of a document.
156
         * <br/>
157
         * Tasks may use this information to work on modified document parts only.
158
         * <br/>
159
         * Note: unlike in some other root element implementations here the child elements
160
         * do not fully cover the root element's offset space.
161
         *
162
         * @return root element containing modified regions of document as child elements.
163
         *  Null if document's implementation does not support modified elements collecting.
164
         */
165
        public Element getModificationsRootElement() {
166
            return ModRootElement.get(doc);
167
        }
168
169
        void setUndoEdit(UndoableEdit undoEdit) {
170
            this.undoEdit = undoEdit;
171
        }
172
        
173
        void setTaskStarted(boolean taskStarted) {
174
            this.taskStarted = taskStarted;
175
        }
176
177
    }
178
179
    static final class PackageAccessor extends DocumentSpiPackageAccessor {
180
181
        @Override
182
        public Context createContext(Document doc) {
183
            return new Context(doc);
184
        }
185
186
        @Override
187
        public void setUndoEdit(Context context, UndoableEdit undoEdit) {
188
            context.setUndoEdit(undoEdit);
189
        }
190
        
191
        @Override
192
        public void setTaskStarted(Context context, boolean taskStarted) {
193
            context.setTaskStarted(taskStarted);
194
        }
195
196
    }
197
198
}
(-)a/editor/src/org/netbeans/modules/editor/EditorModule.java (-2 lines)
Lines 307-314 Link Here
307
                }
307
                }
308
            }
308
            }
309
        });
309
        });
310
311
        ReformatBeforeSaveTask.ensureRegistered();
312
    }
310
    }
313
    
311
    
314
    /** Called when module is uninstalled. Overrides superclass method. */
312
    /** Called when module is uninstalled. Overrides superclass method. */
(-)a/editor/src/org/netbeans/modules/editor/impl/ReformatBeforeSaveTask.java (-169 / +172 lines)
Lines 42-49 Link Here
42
package org.netbeans.modules.editor.impl;
42
package org.netbeans.modules.editor.impl;
43
43
44
import java.util.ArrayList;
44
import java.util.ArrayList;
45
import java.util.LinkedList;
46
import java.util.List;
45
import java.util.List;
46
import java.util.concurrent.atomic.AtomicBoolean;
47
import java.util.logging.Level;
47
import java.util.logging.Level;
48
import java.util.logging.Logger;
48
import java.util.logging.Logger;
49
import java.util.prefs.Preferences;
49
import java.util.prefs.Preferences;
Lines 54-59 Link Here
54
import javax.swing.undo.UndoableEdit;
54
import javax.swing.undo.UndoableEdit;
55
import org.netbeans.api.editor.mimelookup.MimeLookup;
55
import org.netbeans.api.editor.mimelookup.MimeLookup;
56
import org.netbeans.api.editor.mimelookup.MimePath;
56
import org.netbeans.api.editor.mimelookup.MimePath;
57
import org.netbeans.api.editor.mimelookup.MimeRegistration;
57
import org.netbeans.api.editor.settings.SimpleValueNames;
58
import org.netbeans.api.editor.settings.SimpleValueNames;
58
import org.netbeans.editor.GuardedDocument;
59
import org.netbeans.editor.GuardedDocument;
59
import org.netbeans.editor.MarkBlock;
60
import org.netbeans.editor.MarkBlock;
Lines 62-71 Link Here
62
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
63
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
63
import org.netbeans.lib.editor.util.swing.PositionRegion;
64
import org.netbeans.lib.editor.util.swing.PositionRegion;
64
import org.netbeans.modules.editor.indent.api.Reformat;
65
import org.netbeans.modules.editor.indent.api.Reformat;
65
import org.netbeans.modules.editor.lib.BeforeSaveTasks;
66
import org.netbeans.modules.editor.lib2.document.DocumentInternalUtils;
66
import org.netbeans.modules.editor.lib2.document.DocumentInternalUtils;
67
import org.netbeans.modules.editor.lib2.document.ModRootElement;
67
import org.netbeans.modules.editor.lib2.document.ModRootElement;
68
import org.netbeans.modules.editor.lib2.document.TrailingWhitespaceRemoveProcessor;
68
import org.netbeans.spi.editor.document.OnSaveTask;
69
import org.openide.util.Exceptions;
69
import org.openide.util.Exceptions;
70
70
71
/**
71
/**
Lines 73-267 Link Here
73
 *
73
 *
74
 * @author Miloslav Metelka
74
 * @author Miloslav Metelka
75
 */
75
 */
76
public class ReformatBeforeSaveTask implements BeforeSaveTasks.Task {
76
public class ReformatBeforeSaveTask implements OnSaveTask {
77
78
    private static final ReformatBeforeSaveTask INSTANCE = new ReformatBeforeSaveTask();
79
80
    public static void ensureRegistered() {
81
        if (BeforeSaveTasks.getTask(ReformatBeforeSaveTask.class) == null) {
82
            BeforeSaveTasks.addTask(INSTANCE);
83
        }
84
    }
85
77
86
    // -J-Dorg.netbeans.modules.editor.impl.ReformatAtSaveTask.level=FINE
78
    // -J-Dorg.netbeans.modules.editor.impl.ReformatAtSaveTask.level=FINE
87
    private static final Logger LOG = Logger.getLogger(ReformatBeforeSaveTask.class.getName());
79
    private static final Logger LOG = Logger.getLogger(ReformatBeforeSaveTask.class.getName());
88
80
89
    private ReformatBeforeSaveTask() {
81
    private final Document doc;
82
83
    private Reformat reformat;
84
85
    private boolean modifiedLinesOnly;
86
87
    private List<PositionRegion> guardedBlocks;
88
89
    private int guardedBlockIndex;
90
91
    private Position guardedBlockStartPos;
92
93
    private Position guardedBlockEndPos;
94
    
95
    private AtomicBoolean canceled = new AtomicBoolean();
96
97
    ReformatBeforeSaveTask(Document doc) {
98
        this.doc = doc;
90
    }
99
    }
91
100
92
    @Override
101
    @Override
93
    public Object lock(Document doc) {
102
    public void performTask() {
103
        if (reformat != null) {
104
            reformat();
105
        }
106
    }
107
108
    @Override
109
    public void runLocked(Runnable run) {
94
        Preferences prefs = MimeLookup.getLookup(DocumentUtilities.getMimeType(doc)).lookup(Preferences.class);
110
        Preferences prefs = MimeLookup.getLookup(DocumentUtilities.getMimeType(doc)).lookup(Preferences.class);
95
        if (prefs.getBoolean(SimpleValueNames.ON_SAVE_USE_GLOBAL_SETTINGS, Boolean.TRUE)) {
111
        if (prefs.getBoolean(SimpleValueNames.ON_SAVE_USE_GLOBAL_SETTINGS, Boolean.TRUE)) {
96
            prefs = MimeLookup.getLookup(MimePath.EMPTY).lookup(Preferences.class);
112
            prefs = MimeLookup.getLookup(MimePath.EMPTY).lookup(Preferences.class);
97
        }
113
        }
98
        String policy = prefs.get(SimpleValueNames.ON_SAVE_REFORMAT, "never"); //NOI18N
114
        String policy = prefs.get(SimpleValueNames.ON_SAVE_REFORMAT, "never"); //NOI18N
99
        if (!"never".equals(policy)) { //NOI18N
115
        if (!"never".equals(policy)) { //NOI18N
100
            Reformat reformat = Reformat.get(doc);
116
            modifiedLinesOnly = "modified-lines".equals(policy);
117
            reformat = Reformat.get(doc);
101
            reformat.lock();
118
            reformat.lock();
102
            LockInfo lockInfo = new LockInfo(reformat, "modified-lines".equals(policy));
119
            try {
103
            return lockInfo;
120
                run.run();
104
        }
121
            } finally {
105
        return null;
122
                reformat.unlock();
106
    }
123
            }
107
124
        } else {
108
    @Override
125
            run.run();
109
    public void run(Object lock, Document doc, UndoableEdit compoundEdit) {
110
        if (lock != null) {
111
            ((LockInfo)lock).reformat(doc);
112
        }
126
        }
113
    }
127
    }
114
128
115
    @Override
129
    @Override
116
    public void unlock(Object lock, Document doc) {
130
    public boolean cancel() {
117
        if (lock != null) {
131
        canceled.set(true);
118
            ((LockInfo)lock).unlock();
132
        return true;
133
    }
134
    
135
    void reformat() {
136
        ModRootElement modRootElement = ModRootElement.get(doc);
137
        if (modRootElement != null) {
138
            boolean origEnabled = modRootElement.isEnabled();
139
            modRootElement.setEnabled(false);
140
            try {
141
                // Read all guarded blocks
142
                guardedBlocks = new GapList<PositionRegion>();
143
                if (doc instanceof GuardedDocument) {
144
                    MarkBlock block = ((GuardedDocument) doc).getGuardedBlockChain().getChain();
145
                    while (block != null) {
146
                        try {
147
                            guardedBlocks.add(new PositionRegion(doc, block.getStartOffset(), block.getEndOffset()));
148
                        } catch (BadLocationException ex) {
149
                            Exceptions.printStackTrace(ex);
150
                        }
151
                        block = block.getNext();
152
                    }
153
154
                }
155
156
                guardedBlockIndex = 0;
157
                fetchNextGuardedBlock();
158
                Element modRootOrDocElement = (modifiedLinesOnly)
159
                        ? modRootElement
160
                        : DocumentInternalUtils.customElement(doc, 0, doc.getLength());
161
                int modElementCount = modRootOrDocElement.getElementCount();
162
                List<PositionRegion> formatBlocks = new ArrayList<PositionRegion>(modElementCount);
163
                for (int i = 0; i < modElementCount; i++) {
164
                    if (canceled.get()) {
165
                        return;
166
                    }
167
                    Element modElement = modRootOrDocElement.getElement(i);
168
                    boolean modElementFinished;
169
                    boolean add;
170
                    int startOffset = modElement.getStartOffset();
171
                    int modElementEndOffset = modElement.getEndOffset();
172
                    int endOffset = modElementEndOffset;
173
                    do {
174
                        if (guardedBlockStartPos != null) {
175
                            BlockCompare blockCompare = BlockCompare.get(
176
                                    startOffset,
177
                                    endOffset,
178
                                    guardedBlockStartPos.getOffset(),
179
                                    guardedBlockEndPos.getOffset());
180
                            if (blockCompare.before()) {
181
                                add = true;
182
                                modElementFinished = true;
183
                            } else if (blockCompare.after()) {
184
                                fetchNextGuardedBlock();
185
                                add = false;
186
                                modElementFinished = false;
187
                            } else if (blockCompare.equal()) {
188
                                fetchNextGuardedBlock();
189
                                add = false;
190
                                modElementFinished = true;
191
                            } else if (blockCompare.overlapStart()) {
192
                                endOffset = guardedBlockStartPos.getOffset();
193
                                add = true;
194
                                modElementFinished = true;
195
                            } else if (blockCompare.overlapEnd()) {
196
                                // Skip part covered by guarded block
197
                                endOffset = guardedBlockEndPos.getOffset();
198
                                fetchNextGuardedBlock();
199
                                add = false;
200
                                modElementFinished = false;
201
                            } else if (blockCompare.contains()) {
202
                                endOffset = guardedBlockStartPos.getOffset();
203
                                add = true;
204
                                modElementFinished = false;
205
                            } else if (blockCompare.inside()) {
206
                                add = false;
207
                                modElementFinished = true;
208
                            } else {
209
                                LOG.info("Unexpected blockCompare=" + blockCompare);
210
                                add = false;
211
                                modElementFinished = true;
212
                            }
213
                        } else {
214
                            add = true;
215
                            modElementFinished = true;
216
                        }
217
                        if (add) {
218
                            try {
219
                                if (startOffset != endOffset) {
220
                                    PositionRegion block = new PositionRegion(doc, startOffset, endOffset);
221
                                    if (LOG.isLoggable(Level.FINE)) {
222
                                        LOG.fine("Reformat-at-save: add block=" + block);
223
                                    }
224
                                    formatBlocks.add(block);
225
                                }
226
                            } catch (BadLocationException ex) {
227
                                Exceptions.printStackTrace(ex);
228
                            }
229
                        }
230
                        startOffset = endOffset;
231
                        endOffset = modElementEndOffset;
232
                    } while (!modElementFinished);
233
                }
234
235
                try {
236
                    for (PositionRegion block : formatBlocks) {
237
                        if (canceled.get()) {
238
                            return;
239
                        }
240
                        reformat.reformat(block.getStartOffset(), block.getEndOffset());
241
                    }
242
                } catch (Exception ex) {
243
                    Exceptions.printStackTrace(ex);
244
                }
245
246
            } finally {
247
                modRootElement.setEnabled(origEnabled);
248
            }
119
        }
249
        }
120
    }
250
    }
121
251
122
    private static final class LockInfo {
252
    private void fetchNextGuardedBlock() {
123
        
253
        if (guardedBlockIndex < guardedBlocks.size()) {
124
        final Reformat reformat;
254
            PositionRegion guardedBlock = guardedBlocks.get(guardedBlockIndex++);
255
            guardedBlockStartPos = guardedBlock.getStartPosition();
256
            guardedBlockEndPos = guardedBlock.getEndPosition();
257
        } else {
258
            guardedBlockEndPos = guardedBlockStartPos = null;
259
        }
260
    }
125
261
126
        final boolean modifiedLinesOnly;
262
    @MimeRegistration(mimeType="", service=OnSaveTask.Factory.class, position=500)
127
        
263
    public static final class FactoryImpl implements Factory {
128
        List<PositionRegion> guardedBlocks;
129
264
130
        int guardedBlockIndex;
265
        @Override
131
266
        public OnSaveTask createTask(Context context) {
132
        Position guardedBlockStartPos;
267
            return new ReformatBeforeSaveTask(context.getDocument());
133
134
        Position guardedBlockEndPos;
135
136
137
        public LockInfo(Reformat reformat, boolean modifiedLinesOnly) {
138
            this.reformat = reformat;
139
            this.modifiedLinesOnly = modifiedLinesOnly;
140
        }
141
142
        void reformat(Document doc) {
143
            ModRootElement modRootElement = ModRootElement.get(doc);
144
            if (modRootElement != null) {
145
                boolean origEnabled = modRootElement.isEnabled();
146
                modRootElement.setEnabled(false);
147
                try {
148
                    // Read all guarded blocks
149
                    guardedBlocks = new GapList<PositionRegion>();
150
                    if (doc instanceof GuardedDocument) {
151
                        MarkBlock block = ((GuardedDocument)doc).getGuardedBlockChain().getChain();
152
                        while (block != null) {
153
                            try {
154
                                guardedBlocks.add(new PositionRegion(doc, block.getStartOffset(), block.getEndOffset()));
155
                            } catch (BadLocationException ex) {
156
                                Exceptions.printStackTrace(ex);
157
                            }
158
                            block = block.getNext();
159
                        }
160
161
                    }
162
163
                    guardedBlockIndex = 0;
164
                    fetchNextGuardedBlock();
165
                    Element modRootOrDocElement = (modifiedLinesOnly)
166
                            ? modRootElement
167
                            : DocumentInternalUtils.customElement(doc, 0, doc.getLength());
168
                    int modElementCount = modRootOrDocElement.getElementCount();
169
                    List<PositionRegion> formatBlocks = new ArrayList<PositionRegion>(modElementCount);
170
                    for (int i = 0; i < modElementCount; i++) {
171
                        Element modElement = modRootOrDocElement.getElement(i);
172
                        boolean modElementFinished;
173
                        boolean add;
174
                        int startOffset = modElement.getStartOffset();
175
                        int modElementEndOffset = modElement.getEndOffset();
176
                        int endOffset = modElementEndOffset;
177
                        do {
178
                            if (guardedBlockStartPos != null) {
179
                                BlockCompare blockCompare = BlockCompare.get(
180
                                        startOffset,
181
                                        endOffset,
182
                                        guardedBlockStartPos.getOffset(),
183
                                        guardedBlockEndPos.getOffset());
184
                                if (blockCompare.before()) {
185
                                    add = true;
186
                                    modElementFinished = true;
187
                                } else if (blockCompare.after()) {
188
                                    fetchNextGuardedBlock();
189
                                    add = false;
190
                                    modElementFinished = false;
191
                                } else if (blockCompare.equal()) {
192
                                    fetchNextGuardedBlock();
193
                                    add = false;
194
                                    modElementFinished = true;
195
                                } else if (blockCompare.overlapStart()) {
196
                                    endOffset = guardedBlockStartPos.getOffset();
197
                                    add = true;
198
                                    modElementFinished = true;
199
                                } else if (blockCompare.overlapEnd()) {
200
                                    // Skip part covered by guarded block
201
                                    endOffset = guardedBlockEndPos.getOffset();
202
                                    fetchNextGuardedBlock();
203
                                    add = false;
204
                                    modElementFinished = false;
205
                                } else if (blockCompare.contains()) {
206
                                    endOffset = guardedBlockStartPos.getOffset();
207
                                    add = true;
208
                                    modElementFinished = false;
209
                                } else if (blockCompare.inside()) {
210
                                    add = false;
211
                                    modElementFinished = true;
212
                                } else {
213
                                    LOG.info("Unexpected blockCompare=" + blockCompare);
214
                                    add = false;
215
                                    modElementFinished = true;
216
                                }
217
                            } else {
218
                                add = true;
219
                                modElementFinished = true;
220
                            }
221
                            if (add) {
222
                                try {
223
                                    if (startOffset != endOffset) {
224
                                        PositionRegion block = new PositionRegion(doc, startOffset, endOffset);
225
                                        if (LOG.isLoggable(Level.FINE)) {
226
                                            LOG.fine("Reformat-at-save: add block=" + block);
227
                                        }
228
                                        formatBlocks.add(block);
229
                                    }
230
                                } catch (BadLocationException ex) {
231
                                    Exceptions.printStackTrace(ex);
232
                                }
233
                            }
234
                            startOffset = endOffset;
235
                            endOffset = modElementEndOffset;
236
                        } while (!modElementFinished);
237
                    }
238
239
                    try {
240
                        for (PositionRegion block : formatBlocks) {
241
                            reformat.reformat(block.getStartOffset(), block.getEndOffset());
242
                        }
243
                    } catch (Exception ex) {
244
                        Exceptions.printStackTrace(ex);
245
                    }
246
247
                } finally {
248
                    modRootElement.setEnabled(origEnabled);
249
                }
250
            }
251
        }
252
253
        private void fetchNextGuardedBlock() {
254
            if (guardedBlockIndex < guardedBlocks.size()) {
255
                PositionRegion guardedBlock = guardedBlocks.get(guardedBlockIndex++);
256
                guardedBlockStartPos = guardedBlock.getStartPosition();
257
                guardedBlockEndPos = guardedBlock.getEndPosition();
258
            } else {
259
                guardedBlockEndPos = guardedBlockStartPos = null;
260
            }
261
        }
262
263
        void unlock() {
264
            reformat.unlock();
265
        }
268
        }
266
269
267
    }
270
    }

Return to bug 217904