org.netbeans.modules.xml.multiview.XmlMultiViewDataObject Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.xml.multiview;
import org.netbeans.core.spi.multiview.MultiViewElement;
import org.openide.cookies.SaveCookie;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileAlreadyLockedException;
import org.openide.loaders.DataObjectExistsException;
import org.openide.loaders.MultiDataObject;
import org.openide.loaders.MultiFileLoader;
import org.openide.nodes.CookieSet;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.windows.CloneableTopComponent;
import org.openide.ErrorManager;
import org.openide.NotifyDescriptor;
import org.openide.DialogDisplayer;
import org.openide.util.io.ReaderInputStream;
import org.openide.util.NbBundle;
import java.io.*;
import java.util.Enumeration;
import java.util.Date;
import java.lang.ref.WeakReference;
import org.netbeans.modules.xml.api.XmlFileEncodingQueryImpl;
import org.netbeans.spi.queries.FileEncodingQueryImplementation;
/**
* Base class for data objects that are used as a basis for
* the xml multiview. Provides support for caching data ({@link DataCache}), switching
* view, encoding and keeping track of currently active multiview element. Furthermore, it
* associates XmlMultiViewEditorSupport
with this data object.
*
* Created on October 5, 2004, 10:49 AM
* @author mkuchtiak
*/
public abstract class XmlMultiViewDataObject extends MultiDataObject implements CookieSet.Factory {
public static final String PROP_DOCUMENT_VALID = "document_valid"; //NOI18N
public static final String PROP_SAX_ERROR = "sax_error"; //NOI18N
public static final String PROPERTY_DATA_MODIFIED = "data modified"; //NOI18N
public static final String PROPERTY_DATA_UPDATED = "data changed"; //NOI18N
protected XmlMultiViewEditorSupport editorSupport;
private org.xml.sax.SAXException saxError;
private final DataCache dataCache = new DataCache();
private EncodingHelper encodingHelper = new EncodingHelper();
private transient long timeStamp = 0;
private transient WeakReference lockReference;
private MultiViewElement activeMVElement;
private final SaveCookie saveCookie = new SaveCookie() {
/** Implements SaveCookie
interface. */
public void save() throws java.io.IOException {
getEditorSupport().saveDocument();
}
};
/** Creates a new instance of XmlMultiViewDataObject */
public XmlMultiViewDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException {
super(pf, loader);
getCookieSet().add(XmlMultiViewEditorSupport.class, this);
getCookieSet().assign(FileEncodingQueryImplementation.class, new Object[]{XmlFileEncodingQueryImpl.singleton()});
}
protected EditorCookie createEditorCookie() {
return getEditorSupport();
}
protected String getEditorMimeType() {
return null;
}
public org.openide.nodes.Node.Cookie createCookie(Class clazz) {
if (clazz.isAssignableFrom(XmlMultiViewEditorSupport.class)) {
return getEditorSupport();
} else {
return null;
}
}
/** Gets editor support for this data object. */
protected synchronized XmlMultiViewEditorSupport getEditorSupport() {
if(editorSupport == null) {
editorSupport = new XmlMultiViewEditorSupport(this);
editorSupport.getMultiViewDescriptions();
}
return editorSupport;
}
/** enables to switch quickly to XML perspective in multi view editor
*/
public void goToXmlView() {
getEditorSupport().goToXmlPerspective();
}
protected void setSaxError(org.xml.sax.SAXException saxError) {
org.xml.sax.SAXException oldError = this.saxError;
this.saxError=saxError;
if (oldError==null) {
if (saxError != null) {
firePropertyChange(PROP_DOCUMENT_VALID, Boolean.TRUE, Boolean.FALSE);
}
} else {
if (saxError == null) {
firePropertyChange(PROP_DOCUMENT_VALID, Boolean.FALSE, Boolean.TRUE);
}
}
String oldErrorMessage = getErrorMessage(oldError);
String newErrorMessage = getErrorMessage(saxError);
if (oldErrorMessage==null) {
if (newErrorMessage!=null) {
firePropertyChange(PROP_SAX_ERROR, null, newErrorMessage);
}
} else if (!oldErrorMessage.equals(newErrorMessage)) {
firePropertyChange(PROP_SAX_ERROR, oldErrorMessage, newErrorMessage);
}
}
private static String getErrorMessage(Exception e) {
return e == null ? null : e.getMessage();
}
public org.xml.sax.SAXException getSaxError() {
return saxError;
}
/** Icon for XML View */
protected java.awt.Image getXmlViewIcon() {
return ImageUtilities.loadImage("org/netbeans/modules/xml/multiview/resources/xmlObject.gif"); //NOI18N
}
/** MultiViewDesc for MultiView editor
*/
protected DesignMultiViewDesc[] getMultiViewDesc() {
return new DesignMultiViewDesc[0];
}
public void setLastOpenView(int index) {
getEditorSupport().setLastOpenView(index);
}
/** provides renaming of super top component */
protected FileObject handleRename(String name) throws IOException {
FileObject retValue = super.handleRename(name);
getEditorSupport().updateDisplayName();
return retValue;
}
@Override
public Lookup getLookup() {
return getCookieSet().getLookup();
}
/**
* Set whether the object is considered modified.
* Also fires a change event.
* If the new value is true
, the data object is added into a {@link #getRegistry registry} of opened data objects.
* If the new value is false
,
* the data object is removed from the registry.
*/
public void setModified(boolean modif) {
super.setModified(modif);
//getEditorSupport().updateDisplayName();
if (isModified()) {
// Add save cookie
if (getCookie(SaveCookie.class) == null) {
getCookieSet().add(saveCookie);
}
} else {
// Remove save cookie
if(saveCookie.equals(getCookie(SaveCookie.class))) {
getCookieSet().remove(saveCookie);
}
}
}
public boolean canClose() {
final CloneableTopComponent topComponent = ((CloneableTopComponent) getEditorSupport().getMVTC());
if (topComponent != null){
Enumeration enumeration = topComponent.getReference().getComponents();
if (enumeration.hasMoreElements()) {
enumeration.nextElement();
if (enumeration.hasMoreElements()) {
return true;
}
}
}
FileLock lock;
try {
lock = waitForLock();
} catch (IOException e) {
ErrorManager.getDefault().notify(e);
return !isModified();
}
try {
return !isModified();
} finally {
lock.releaseLock();
}
}
public FileLock waitForLock() throws IOException {
return waitForLock(10000);
}
public FileLock waitForLock(long timeout) throws IOException {
long t = System.currentTimeMillis() + timeout;
long sleepTime = 50;
for (;;) {
try {
return dataCache.lock();
} catch (IOException e) {
if (System.currentTimeMillis() > t) {
throw (IOException) new IOException("Cannot wait for data lock for more than " + timeout + " ms").initCause(e); //NO18N
}
try {
Thread.sleep(sleepTime);
sleepTime = 3 * sleepTime / 2;
} catch (InterruptedException e1) {
//
}
}
}
}
public org.netbeans.core.api.multiview.MultiViewPerspective getSelectedPerspective() {
return getEditorSupport().getSelectedPerspective();
}
/** Enable to focus specific object in Multiview Editor
* The default implementation opens the XML View.
*/
public void showElement(Object element) {
getEditorSupport().edit();
}
/** Enable to get active MultiViewElement object
*/
protected MultiViewElement getActiveMultiViewElement() {
return activeMVElement;
}
void setActiveMultiViewElement(MultiViewElement element) {
activeMVElement = element;
}
/** Opens the specific view
* @param index multi-view index
*/
public void openView(int index) {
getEditorSupport().openView(index);
}
protected abstract String getPrefixMark();
boolean acceptEncoding() throws IOException {
String encoding = encoding();
if (encodingDiffer(encoding)) {
Object result = showChangeEncodingDialog(encoding);
if (NotifyDescriptor.YES_OPTION.equals(result)) {
encodingReset();
} else if (NotifyDescriptor.NO_OPTION.equals(result)) {
showUsingDifferentEncodingMessage(encoding);
} else {
return false;
}
}
return true;
}
String encoding() throws IOException {
encodingHelper.resetEncoding();
DataCache dataCache = getDataCache();
String s = dataCache.getStringData();
String encoding = encodingHelper.detectEncoding(s.getBytes());
return encoding;
}
boolean encodingDiffer(String encoding) {
return !encodingHelper.getEncoding().equals(encoding);
}
String encodingMessage(String encoding) {
return NbBundle.getMessage(XmlMultiViewDataObject.class,
"TEXT_TREAT_USING_DIFFERENT_ENCODING",
encoding, encodingHelper.getEncoding());
}
void encodingReset() {
DataCache dataCache = getDataCache();
String s = dataCache.getStringData();
dataCache.setData(encodingHelper.setDefaultEncoding(s));
}
private void showUsingDifferentEncodingMessage(String encoding) {
String message = encodingMessage(encoding);
NotifyDescriptor.Message descriptor = new NotifyDescriptor.Message(message);
descriptor.setTitle(getPrimaryFile().getPath());
DialogDisplayer.getDefault().notify(descriptor);
}
private Object showChangeEncodingDialog(String encoding) {
String message = NbBundle.getMessage(Utils.class, "TEXT_CHANGE_DECLARED_ENCODING", encoding,
encodingHelper.getEncoding());
NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(message, getPrimaryFile().getPath(),
NotifyDescriptor.YES_NO_CANCEL_OPTION);
return DialogDisplayer.getDefault().notify(descriptor);
}
public EncodingHelper getEncodingHelper() {
return encodingHelper;
}
public DataCache getDataCache() {
return dataCache;
}
/**
*
* @return index for source view
*/
protected int getXMLMultiViewIndex() {
return 0;
}
/** Is that necesary for this class to be public ?
* It can be changed to interface
*/
public class DataCache {
// What about using the StringBuffer instead ?
private transient String buffer = null;
private long fileTime = 0;
public void loadData() {
FileObject file = getPrimaryFile();
if (fileTime == file.lastModified().getTime()) {
// on base of issue #132922
// when the file is deleted or IO error happened, lastModified time
// is zero and the buffer can stay uninitialized
if (fileTime == 0) {
buffer = ""; //NOI18N
}
return;
}
try {
FileLock dataLock = lock();
loadData(file, dataLock);
} catch (IOException e) {
if (buffer == null) {
buffer = ""; //NOI18N
}
}
}
/**
* Updates the data cache with the contents of the associated file.
* Unlike {@link #loadData()}, tries to use existing lock before attempting
* to acquire a new lock.
*/
public void reloadData() throws IOException{
FileObject file = getPrimaryFile();
if (fileTime == file.lastModified().getTime()) {
return;
}
FileLock lock;
synchronized (this) {
lock = getLock();
if (lock == null){
lock = lock();
}
}
loadData(file, lock);
}
/** Does this method need to be public ?
*/
public void loadData(FileObject file, FileLock dataLock) throws IOException {
try {
BufferedInputStream inputStream = new BufferedInputStream(file.getInputStream());
String encoding = encodingHelper.detectEncoding(inputStream);
if (!encodingHelper.getEncoding().equals(encoding)) {
showUsingDifferentEncodingMessage(encoding);
}
Reader reader = new InputStreamReader(inputStream, encodingHelper.getEncoding());
long time;
StringBuffer sb = new StringBuffer(2048);
try {
char[] buf = new char[1024];
time = file.lastModified().getTime();
int i;
while ((i = reader.read(buf,0,1024)) != -1) {
sb.append(buf,0,i);
}
} finally {
reader.close();
}
buffer = null;
fileTime = time;
setData(dataLock, sb.toString(), true);
} finally {
dataLock.releaseLock();
}
}
/** Is the second argument necessary ?
*/
public void setData(FileLock lock, String s, boolean modify) throws IOException {
testLock(lock);
boolean modified = isModified() || modify;
long oldTimeStamp = timeStamp;
if (setData(s)) {
if (!modified) {
saveData(lock);
firePropertyChange(PROPERTY_DATA_UPDATED, new Long(oldTimeStamp), new Long(timeStamp));
} else {
firePropertyChange(PROPERTY_DATA_MODIFIED, new Long(oldTimeStamp), new Long(timeStamp));
}
}
}
private boolean setData(String s) {
// ??? when this can happen
if (s.equals(buffer)) {
return false;
}
buffer = s;
long newTimeStamp = new Date().getTime();
if (newTimeStamp <= timeStamp) {
newTimeStamp = timeStamp + 1;
}
timeStamp = newTimeStamp;
fileTime = 0;
return true;
}
public synchronized void saveData(FileLock dataLock) {
if (buffer == null || fileTime == getPrimaryFile().lastModified().getTime()) {
return;
}
try {
XmlMultiViewEditorSupport editorSupport = getEditorSupport();
if (editorSupport.getDocument() == null) {
XmlMultiViewEditorSupport.XmlEnv xmlEnv = editorSupport.getXmlEnv();
FileLock lock = null;
try {
lock = xmlEnv.takeLock();
OutputStream outputStream = getPrimaryFile().getOutputStream(lock);
Writer writer = new OutputStreamWriter(outputStream, encodingHelper.getEncoding());
try {
writer.write(buffer);
} finally {
writer.close();
xmlEnv.unmarkModified();
resetFileTime();
}
} finally {
if (lock != null) {
lock.releaseLock();
}
}
} else {
editorSupport.saveDocument(dataLock);
}
} catch (IOException e) {
ErrorManager.getDefault().notify(e);
}
}
public FileLock lock() throws IOException {
synchronized (this) {
FileLock current = getLock();
if (current != null) {
throw new FileAlreadyLockedException("File is already locked by [" + current + "]."); // NO18N
}
FileLock l = new FileLock();
lockReference = new WeakReference<>(l);
return l;
}
}
private synchronized FileLock getLock() {
// How this week reference can be useful ?
FileLock l = lockReference == null ? null : lockReference.get();
if (l != null && !l.isValid()) {
l = null;
}
return l;
}
public String getStringData() {
if (buffer == null) {
loadData();
}
return buffer;
}
public byte[] getData() {
try {
return getStringData().getBytes(encodingHelper.getEncoding());
} catch (UnsupportedEncodingException e) {
ErrorManager.getDefault().notify(e);
return null; // should not happen
}
}
public void setData(FileLock lock, byte[] data, boolean modify) throws IOException {
encodingHelper.detectEncoding(data);
setData(lock, new String(data, encodingHelper.getEncoding()), modify);
}
public long getTimeStamp() {
return timeStamp;
}
public InputStream createInputStream() {
try {
encodingHelper.detectEncoding(getStringData().getBytes());
return new ReaderInputStream(new StringReader(getStringData()), encodingHelper.getEncoding());
} catch (IOException e) {
ErrorManager.getDefault().notify(e);
return null;
}
}
public Reader createReader() throws IOException {
return new StringReader(getStringData());
}
public OutputStream createOutputStream() throws IOException {
final FileLock dataLock = lock();
return new ByteArrayOutputStream() {
public void close() throws IOException {
try {
super.close();
setData(dataLock, toByteArray(), true);
} finally {
dataLock.releaseLock();
}
}
};
}
public OutputStream createOutputStream(final FileLock dataLock, final boolean modify) throws IOException {
testLock(dataLock);
return new ByteArrayOutputStream() {
public void close() throws IOException {
super.close();
setData(dataLock, toByteArray(), modify);
if (!modify) {
dataCache.saveData(dataLock);
}
}
};
}
public Writer createWriter() throws IOException {
final FileLock dataLock = lock();
return new StringWriter() {
public void close() throws IOException {
try {
super.close();
setData(dataLock, toString(), true);
} finally {
dataLock.releaseLock();
}
}
};
}
public Writer createWriter(final FileLock dataLock, final boolean modify) throws IOException {
testLock(dataLock);
return new StringWriter() {
public void close() throws IOException {
super.close();
setData(dataLock, toString(), modify);
if (!modify) {
dataCache.saveData(dataLock);
}
}
};
}
public void testLock(FileLock lock) throws IOException {
if (lock == null) {
throw new IOException("Lock is null."); //NO18N
} else if (lock != getLock()){
throw new IOException("Invalid lock [" + lock + "]. Expected [" + getLock() + "]."); //NO18N
}
}
public void resetFileTime() {
fileTime = getPrimaryFile().lastModified().getTime();
}
}
/** Access point for inheritors to verify document before close.
*
* @return true if document is valid, false otherwise
*/
protected boolean verifyDocumentBeforeClose() {
return true;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy