2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.netbeans.modules.xml.multiview;
44
import org.netbeans.core.spi.multiview.MultiViewElement;
45
import org.openide.cookies.SaveCookie;
46
import org.openide.cookies.EditorCookie;
47
import org.openide.filesystems.FileObject;
48
import org.openide.filesystems.FileLock;
49
import org.openide.filesystems.FileAlreadyLockedException;
50
import org.openide.loaders.DataObjectExistsException;
51
import org.openide.loaders.MultiDataObject;
52
import org.openide.loaders.MultiFileLoader;
53
import org.openide.nodes.CookieSet;
54
import org.openide.util.Lookup;
55
import org.openide.windows.CloneableTopComponent;
56
import org.openide.ErrorManager;
57
import org.openide.NotifyDescriptor;
58
import org.openide.DialogDisplayer;
59
import org.openide.util.io.ReaderInputStream;
60
import org.openide.util.NbBundle;
63
import java.util.Enumeration;
64
import java.util.Date;
65
import java.lang.ref.WeakReference;
66
import org.netbeans.modules.xml.api.XmlFileEncodingQueryImpl;
67
import org.netbeans.spi.queries.FileEncodingQueryImplementation;
70
* Base class for data objects that are used as a basis for
71
* the xml multiview. Provides support for caching data ({@link DataCache}), switching
72
* view, encoding and keeping track of currently active multiview element. Furthermore, it
73
* associates <code>XmlMultiViewEditorSupport</code> with this data object.
75
* Created on October 5, 2004, 10:49 AM
78
public abstract class XmlMultiViewDataObject extends MultiDataObject implements CookieSet.Factory {
80
public static final String PROP_DOCUMENT_VALID = "document_valid"; //NOI18N
81
public static final String PROP_SAX_ERROR = "sax_error"; //NOI18N
82
public static final String PROPERTY_DATA_MODIFIED = "data modified"; //NOI18N
83
public static final String PROPERTY_DATA_UPDATED = "data changed"; //NOI18N
84
protected XmlMultiViewEditorSupport editorSupport;
85
private org.xml.sax.SAXException saxError;
87
private final DataCache dataCache = new DataCache();
88
private EncodingHelper encodingHelper = new EncodingHelper();
89
private transient long timeStamp = 0;
90
private transient WeakReference lockReference;
93
private MultiViewElement activeMVElement;
95
private final SaveCookie saveCookie = new SaveCookie() {
96
/** Implements <code>SaveCookie</code> interface. */
97
public void save() throws java.io.IOException {
98
getEditorSupport().saveDocument();
102
/** Creates a new instance of XmlMultiViewDataObject */
103
public XmlMultiViewDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException {
105
getCookieSet().add(XmlMultiViewEditorSupport.class, this);
106
getCookieSet().assign(FileEncodingQueryImplementation.class, new Object[]{XmlFileEncodingQueryImpl.singleton()});
109
protected EditorCookie createEditorCookie() {
110
return getEditorSupport();
113
public org.openide.nodes.Node.Cookie createCookie(Class clazz) {
114
if (clazz.isAssignableFrom(XmlMultiViewEditorSupport.class)) {
115
return getEditorSupport();
121
/** Gets editor support for this data object. */
122
protected synchronized XmlMultiViewEditorSupport getEditorSupport() {
123
if(editorSupport == null) {
124
editorSupport = new XmlMultiViewEditorSupport(this);
125
editorSupport.getMultiViewDescriptions();
127
return editorSupport;
130
/** enables to switch quickly to XML perspective in multi view editor
132
public void goToXmlView() {
133
getEditorSupport().goToXmlPerspective();
136
protected void setSaxError(org.xml.sax.SAXException saxError) {
137
org.xml.sax.SAXException oldError = this.saxError;
138
this.saxError=saxError;
139
if (oldError==null) {
140
if (saxError != null) {
141
firePropertyChange(PROP_DOCUMENT_VALID, Boolean.TRUE, Boolean.FALSE);
144
if (saxError == null) {
145
firePropertyChange(PROP_DOCUMENT_VALID, Boolean.FALSE, Boolean.TRUE);
149
String oldErrorMessage = getErrorMessage(oldError);
150
String newErrorMessage = getErrorMessage(saxError);
151
if (oldErrorMessage==null) {
152
if (newErrorMessage!=null) {
153
firePropertyChange(PROP_SAX_ERROR, null, newErrorMessage);
155
} else if (!oldErrorMessage.equals(newErrorMessage)) {
156
firePropertyChange(PROP_SAX_ERROR, oldErrorMessage, newErrorMessage);
160
private static String getErrorMessage(Exception e) {
161
return e == null ? null : e.getMessage();
164
public org.xml.sax.SAXException getSaxError() {
168
/** Icon for XML View */
169
protected java.awt.Image getXmlViewIcon() {
170
return org.openide.util.Utilities.loadImage("org/netbeans/modules/xml/multiview/resources/xmlObject.gif"); //NOI18N
173
/** MultiViewDesc for MultiView editor
175
protected abstract DesignMultiViewDesc[] getMultiViewDesc();
177
public void setLastOpenView(int index) {
178
getEditorSupport().setLastOpenView(index);
181
/** provides renaming of super top component */
182
protected FileObject handleRename(String name) throws IOException {
183
FileObject retValue = super.handleRename(name);
184
getEditorSupport().updateDisplayName();
188
public Lookup getLookup() {
189
return getCookieSet().getLookup();
193
* Set whether the object is considered modified.
194
* Also fires a change event.
195
* If the new value is <code>true</code>, the data object is added into a {@link #getRegistry registry} of opened data objects.
196
* If the new value is <code>false</code>,
197
* the data object is removed from the registry.
199
public void setModified(boolean modif) {
200
super.setModified(modif);
201
//getEditorSupport().updateDisplayName();
204
if (getCookie(SaveCookie.class) == null) {
205
getCookieSet().add(saveCookie);
208
// Remove save cookie
209
if(saveCookie.equals(getCookie(SaveCookie.class))) {
210
getCookieSet().remove(saveCookie);
216
public boolean canClose() {
217
final CloneableTopComponent topComponent = ((CloneableTopComponent) getEditorSupport().getMVTC());
218
if (topComponent != null){
219
Enumeration enumeration = topComponent.getReference().getComponents();
220
if (enumeration.hasMoreElements()) {
221
enumeration.nextElement();
222
if (enumeration.hasMoreElements()) {
229
lock = waitForLock();
230
} catch (IOException e) {
231
ErrorManager.getDefault().notify(e);
232
return !isModified();
235
return !isModified();
241
public FileLock waitForLock() throws IOException {
242
return waitForLock(10000);
245
public FileLock waitForLock(long timeout) throws IOException {
246
long t = System.currentTimeMillis() + timeout;
250
return dataCache.lock();
251
} catch (IOException e) {
252
if (System.currentTimeMillis() > t) {
253
throw (IOException) new IOException("Cannot wait for data lock for more than " + timeout + " ms").initCause(e); //NO18N
256
Thread.sleep(sleepTime);
257
sleepTime = 3 * sleepTime / 2;
258
} catch (InterruptedException e1) {
265
public org.netbeans.core.api.multiview.MultiViewPerspective getSelectedPerspective() {
266
return getEditorSupport().getSelectedPerspective();
269
/** Enable to focus specific object in Multiview Editor
270
* The default implementation opens the XML View.
272
public void showElement(Object element) {
273
getEditorSupport().edit();
276
/** Enable to get active MultiViewElement object
278
protected MultiViewElement getActiveMultiViewElement() {
279
return activeMVElement;
281
void setActiveMultiViewElement(MultiViewElement element) {
282
activeMVElement = element;
284
/** Opens the specific view
285
* @param index multi-view index
287
public void openView(int index) {
288
getEditorSupport().openView(index);
291
protected abstract String getPrefixMark();
293
boolean acceptEncoding() throws IOException {
294
encodingHelper.resetEncoding();
295
DataCache dataCache = getDataCache();
296
String s = dataCache.getStringData();
297
String encoding = encodingHelper.detectEncoding(s.getBytes());
298
if (!encodingHelper.getEncoding().equals(encoding)) {
299
Object result = showChangeEncodingDialog(encoding);
300
if (NotifyDescriptor.YES_OPTION.equals(result)) {
301
dataCache.setData(encodingHelper.setDefaultEncoding(s));
302
} else if (NotifyDescriptor.NO_OPTION.equals(result)) {
303
showUsingDifferentEncodingMessage(encoding);
311
private void showUsingDifferentEncodingMessage(String encoding) {
312
String message = NbBundle.getMessage(XmlMultiViewDataObject.class, "TEXT_TREAT_USING_DIFFERENT_ENCODING", encoding,
313
encodingHelper.getEncoding());
314
NotifyDescriptor.Message descriptor = new NotifyDescriptor.Message(message);
315
descriptor.setTitle(getPrimaryFile().getPath());
316
DialogDisplayer.getDefault().notify(descriptor);
319
private Object showChangeEncodingDialog(String encoding) {
320
String message = NbBundle.getMessage(Utils.class, "TEXT_CHANGE_DECLARED_ENCODING", encoding,
321
encodingHelper.getEncoding());
322
NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(message, getPrimaryFile().getPath(),
323
NotifyDescriptor.YES_NO_CANCEL_OPTION);
324
return DialogDisplayer.getDefault().notify(descriptor);
327
public EncodingHelper getEncodingHelper() {
328
return encodingHelper;
331
public DataCache getDataCache() {
335
/** Is that necesary for this class to be public ?
336
* It can be changed to interface
338
public class DataCache {
340
// What about using the StringBuffer instead ?
341
private transient String buffer = null;
342
private long fileTime = 0;
344
public void loadData() {
345
FileObject file = getPrimaryFile();
346
if (fileTime == file.lastModified().getTime()) {
350
FileLock dataLock = lock();
351
loadData(file, dataLock);
352
} catch (IOException e) {
353
if (buffer == null) {
354
buffer = ""; //NOI18N
360
* Updates the data cache with the contents of the associated file.
361
* Unlike {@link #loadData()}, tries to use existing lock before attempting
362
* to acquire a new lock.
364
public void reloadData() throws IOException{
365
FileObject file = getPrimaryFile();
366
if (fileTime == file.lastModified().getTime()) {
369
FileLock lock = getLock();
373
loadData(file, lock);
376
/** Does this method need to be public ?
378
public void loadData(FileObject file, FileLock dataLock) throws IOException {
380
BufferedInputStream inputStream = new BufferedInputStream(file.getInputStream());
381
String encoding = encodingHelper.detectEncoding(inputStream);
382
if (!encodingHelper.getEncoding().equals(encoding)) {
383
showUsingDifferentEncodingMessage(encoding);
385
Reader reader = new InputStreamReader(inputStream, encodingHelper.getEncoding());
387
StringBuffer sb = new StringBuffer(2048);
389
char[] buf = new char[1024];
390
time = file.lastModified().getTime();
392
while ((i = reader.read(buf,0,1024)) != -1) {
400
setData(dataLock, sb.toString(), true);
402
dataLock.releaseLock();
405
/** Is the second argument necessary ?
407
public void setData(FileLock lock, String s, boolean modify) throws IOException {
409
boolean modified = isModified() || modify;
410
long oldTimeStamp = timeStamp;
414
firePropertyChange(PROPERTY_DATA_UPDATED, new Long(oldTimeStamp), new Long(timeStamp));
416
firePropertyChange(PROPERTY_DATA_MODIFIED, new Long(oldTimeStamp), new Long(timeStamp));
421
private boolean setData(String s) {
422
// ??? when this can happen
423
if (s.equals(buffer)) {
427
long newTimeStamp = new Date().getTime();
428
if (newTimeStamp <= timeStamp) {
429
newTimeStamp = timeStamp + 1;
431
timeStamp = newTimeStamp;
436
public synchronized void saveData(FileLock dataLock) {
437
if (buffer == null || fileTime == getPrimaryFile().lastModified().getTime()) {
442
XmlMultiViewEditorSupport editorSupport = getEditorSupport();
443
if (editorSupport.getDocument() == null) {
444
XmlMultiViewEditorSupport.XmlEnv xmlEnv = editorSupport.getXmlEnv();
445
FileLock lock = xmlEnv.takeLock();
446
OutputStream outputStream = getPrimaryFile().getOutputStream(lock);
447
Writer writer = new OutputStreamWriter(outputStream, encodingHelper.getEncoding());
449
writer.write(buffer);
453
xmlEnv.unmarkModified();
457
editorSupport.saveDocument(dataLock);
459
} catch (IOException e) {
460
ErrorManager.getDefault().notify(e);
464
public FileLock lock() throws IOException {
465
FileLock current = getLock();
466
if (current != null) {
467
throw new FileAlreadyLockedException("File is already locked by [" + current + "]."); // NO18N
469
FileLock l = new FileLock();
470
lockReference = new WeakReference(l);
474
private FileLock getLock() {
475
// How this week reference can be useful ?
476
FileLock l = lockReference == null ? null : (FileLock) lockReference.get();
477
if (l != null && !l.isValid()) {
483
public String getStringData() {
484
if (buffer == null) {
490
public byte[] getData() {
492
return getStringData().getBytes(encodingHelper.getEncoding());
493
} catch (UnsupportedEncodingException e) {
494
ErrorManager.getDefault().notify(e);
495
return null; // should not happen
499
public void setData(FileLock lock, byte[] data, boolean modify) throws IOException {
500
encodingHelper.detectEncoding(data);
501
setData(lock, new String(data, encodingHelper.getEncoding()), modify);
504
public long getTimeStamp() {
508
public InputStream createInputStream() {
510
encodingHelper.detectEncoding(getStringData().getBytes());
511
return new ReaderInputStream(new StringReader(getStringData()), encodingHelper.getEncoding());
512
} catch (IOException e) {
513
ErrorManager.getDefault().notify(e);
518
public Reader createReader() throws IOException {
519
return new StringReader(getStringData());
522
public OutputStream createOutputStream() throws IOException {
523
final FileLock dataLock = lock();
524
return new ByteArrayOutputStream() {
525
public void close() throws IOException {
528
setData(dataLock, toByteArray(), true);
530
dataLock.releaseLock();
536
public OutputStream createOutputStream(final FileLock dataLock, final boolean modify) throws IOException {
538
return new ByteArrayOutputStream() {
539
public void close() throws IOException {
541
setData(dataLock, toByteArray(), modify);
543
dataCache.saveData(dataLock);
549
public Writer createWriter() throws IOException {
550
final FileLock dataLock = lock();
551
return new StringWriter() {
552
public void close() throws IOException {
555
setData(dataLock, toString(), true);
557
dataLock.releaseLock();
563
public Writer createWriter(final FileLock dataLock, final boolean modify) throws IOException {
565
return new StringWriter() {
566
public void close() throws IOException {
568
setData(dataLock, toString(), modify);
570
dataCache.saveData(dataLock);
576
public void testLock(FileLock lock) throws IOException {
578
throw new IOException("Lock is null."); //NO18N
579
} else if (lock != getLock()){
580
throw new IOException("Invalid lock [" + lock + "]. Expected [" + getLock() + "]."); //NO18N
584
public void resetFileTime() {
585
fileTime = getPrimaryFile().lastModified().getTime();