org.openide.filesystems.StreamPool Maven / Gradle / Ivy
/*
* 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.openide.filesystems;
import org.openide.util.*;
import java.io.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class keeps info about streams (these streams are registered) that was
* not closed yet. Also for every issued stream is hold stracktrace.
* Sometimes there is necessary to know who didn`t close stream.
*
* @author rmatous
*/
final class StreamPool extends Object {
static final Logger LOG = Logger.getLogger(StreamPool.class.getName());
private static Map fo2StreamPool = new WeakHashMap();
private static Map fs2StreamPool = new WeakHashMap();
private static Boolean annotateUnclosedStreams;
private Set iStreams;
private Set oStreams;
/** Creates new StreamPool */
private StreamPool() {
}
/**
* This method creates subclassed NotifyInputStream (extends InputStream).
* NotifyInputStream saves stacktrace in constrcuctor (creates new Exception) that
* is used in method annotate.
* This method also register this NotifyInputStream as
* mapping (AbstractFolder, NotifyInputStream) and
* mapping (AbstractFolder.getFileSystem(), NotifyInputStream).
* If NotifyInputStream is closed then registration is freed.
* For fo is also created StreamPool unless it exists yet.
* @param fo FileObject that issues is
* @return subclassed InputStream that is registered as mentioned above */
public static InputStream createInputStream(final AbstractFolder fo)
throws FileNotFoundException {
InputStream retVal = null;
synchronized (StreamPool.class) {
if (get(fo).waitForOutputStreamsClosed(fo, 2000)) {
retVal = new NotifyInputStream(fo);
get(fo).iStream().add(retVal);
get(fo.getFileSystem()).iStream().add(retVal);
}
}
if ((retVal != null) && (retVal instanceof NotifyInputStream)) {
AbstractFileSystem abstractFileSystem = ((AbstractFileSystem) fo.getFileSystem());
((NotifyInputStream) retVal).setOriginal(abstractFileSystem.info.inputStream(fo.getPath()));
} else {
retVal = new InputStream() {
@Override
public int read() throws IOException {
FileAlreadyLockedException alreadyLockedEx = new FileAlreadyLockedException(fo.getPath());
get(fo).annotate(alreadyLockedEx);
throw alreadyLockedEx;
}
};
}
return retVal;
}
/** This method creates subclassed NotifyOutputStream (extends OutputStream).
* NotifyOutputStream saves stacktrace in constrcuctor (creates new Exception) that
* is used in method annotate.
* This method also register this NotifyOutputStream as
* mapping (AbstractFolder, NotifyOutputStream) and
* mapping (AbstractFolder.getFileSystem(), NotifyOutputStream).
* If NotifyOutputStream is closed then registration is freed.
* For fo is also created StreamPool unless it exists yet.
* @return subclassed OutputStream that is registered as mentioned above
* @param fireFileChanged defines if should be fired fileChanged event after close of stream
* @param fo FileObject that issues is
* */
public static OutputStream createOutputStream(final AbstractFolder fo, boolean fireFileChanged)
throws IOException {
OutputStream retVal = null;
synchronized (StreamPool.class) {
if (get(fo).waitForInputStreamsClosed(fo, 2000) &&
get(fo).waitForOutputStreamsClosed(fo, 2000)
) {
retVal = new NotifyOutputStream(fo, fireFileChanged);
get(fo).oStream().add(retVal);
get(fo.getFileSystem()).oStream().add(retVal);
}
}
if ((retVal != null) && (retVal instanceof NotifyOutputStream)) {
AbstractFileSystem abstractFileSystem = ((AbstractFileSystem) fo.getFileSystem());
((NotifyOutputStream) retVal).setOriginal(abstractFileSystem.info.outputStream(fo.getPath()));
} else {
retVal = new OutputStream() {
@Override
public void write(int b) throws IOException {
FileAlreadyLockedException alreadyLockedEx = new FileAlreadyLockedException(fo.getPath());
get(fo).annotate(alreadyLockedEx);
throw alreadyLockedEx;
}
};
}
return retVal;
}
/**
* This method finds StreamPool assiciated with fo or null. This StreamPool is
* created by means of createInputStream or createOutputStream.
* @param fo FileObject whose StreamPool is looked for
* @return StreamPool or null*/
public static synchronized StreamPool find(FileObject fo) {
return fo2StreamPool.get(fo);
}
/**
* This method finds StreamPool assiciated with fs or null. This StreamPool is
* created by means of createInputStream or createOutputStream.
* @param fs FileSystem whose StreamPool is looked for
* @return StreamPool or null*/
public static synchronized StreamPool find(FileSystem fs) {
return fs2StreamPool.get(fs);
}
/**
* Annotates ex with all exceptions of unclosed streams.
* @param ex that should be annotated */
public void annotate(Exception ex) {
if (!annotateUnclosedStreams()) {
return;
}
synchronized (StreamPool.class) {
if (iStreams != null) {
Iterator itIs = iStreams.iterator();
NotifyInputStream nis;
while (itIs.hasNext()) {
nis = (NotifyInputStream) itIs.next();
Exception annotation = nis.getException();
if (annotation != null) {
ExternalUtil.annotate(ex, annotation);
}
}
}
if (oStreams != null) {
Iterator itOs = oStreams.iterator();
NotifyOutputStream nos;
while (itOs.hasNext()) {
nos = (NotifyOutputStream) itOs.next();
Exception annotation = nos.getException();
if (annotation != null) {
ExternalUtil.annotate(ex, annotation);
}
}
}
}
}
/**
* @return true if there is any InputStream that was not closed yet */
public boolean isInputStreamOpen() {
return isStreamOpen(iStreams);
}
/**
* @return true if there is any OutputStream that was not closed yet */
public boolean isOutputStreamOpen() {
return isStreamOpen(oStreams);
}
private boolean waitForInputStreamsClosed(FileObject fo, int timeInMs) {
return waitForStreams(fo, timeInMs, iStreams);
}
private boolean waitForOutputStreamsClosed(FileObject fo, int timeInMs) {
return waitForStreams(fo, timeInMs, oStreams);
}
private static boolean waitForStreams(FileObject fo, int timeInMs, Set> streams) {
synchronized (StreamPool.class) {
if (isStreamOpen(streams)) {
long till = System.currentTimeMillis() + timeInMs;
boolean interrupted = false;
for (;;) {
long wait = till - System.currentTimeMillis();
if (wait <= 0) {
break;
}
try {
StreamPool.class.wait(wait);
break;
} catch (InterruptedException ex) {
interrupted = true;
continue;
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
if (isStreamOpen(streams)) {
LOG.log(Level.FINE, "Open streams {0} for {1}", new Object[]{streams, fo});
return false;
}
}
return true;
}
}
private static boolean isStreamOpen(Set> set) {
return (set != null) && !set.isEmpty();
}
/** All next methods are private (Not visible outside this class)*/
private static StreamPool get(FileObject fo) {
StreamPool strPool = fo2StreamPool.get(fo);
if (strPool == null) {
fo2StreamPool.put(fo, strPool = new StreamPool());
}
return strPool;
}
private static StreamPool get(FileSystem fs) {
StreamPool strPool = fs2StreamPool.get(fs);
if (strPool == null) {
fs2StreamPool.put(fs, strPool = new StreamPool());
}
return strPool;
}
private Set iStream() {
if (iStreams == null) {
iStreams = new WeakSet();
}
return iStreams;
}
private Set oStream() {
if (oStreams == null) {
oStreams = new WeakSet();
}
return oStreams;
}
/** fireFileChange defines if should be fired fileChanged event after close of stream*/
private static void closeOutputStream(AbstractFolder fo, OutputStream os, boolean fireFileChanged) {
StreamPool foPool = find(fo);
StreamPool fsPool = find(fo.getFileSystem());
Set foSet = (foPool != null) ? foPool.oStreams : null;
Set fsSet = (fsPool != null) ? fsPool.oStreams : null;
removeStreams(fsSet, foSet, os);
removeStreamPools(fsPool, foPool, fo);
fo.outputStreamClosed(fireFileChanged);
}
private static void closeInputStream(AbstractFolder fo, InputStream is) {
StreamPool foPool = find(fo);
StreamPool fsPool = find(fo.getFileSystem());
Set foSet = (foPool != null) ? foPool.iStreams : null;
Set fsSet = (fsPool != null) ? fsPool.iStreams : null;
removeStreams(fsSet, foSet, is);
removeStreamPools(fsPool, foPool, fo);
}
private static synchronized void removeStreams(Set fsSet, Set foSet, Object stream) {
if (foSet != null) {
foSet.remove(stream);
}
if (fsSet != null) {
fsSet.remove(stream);
}
}
private static synchronized void removeStreamPools(StreamPool fsPool, StreamPool foPool, AbstractFolder fo) {
boolean isIStreamEmpty = ((foPool == null) || (foPool.iStreams == null) || foPool.iStreams.isEmpty());
boolean isOStreamEmpty = ((foPool == null) || (foPool.oStreams == null) || foPool.oStreams.isEmpty());
if (isIStreamEmpty && isOStreamEmpty) {
fo2StreamPool.remove(fo);
}
isIStreamEmpty = ((fsPool == null) || (fsPool.iStreams == null) || fsPool.iStreams.isEmpty());
isOStreamEmpty = ((fsPool == null) || (fsPool.oStreams == null) || fsPool.oStreams.isEmpty());
if (isIStreamEmpty && isOStreamEmpty) {
fs2StreamPool.remove(fo.getFileSystem());
}
}
private static final class NotifyOutputStream extends FilterOutputStream {
private static final OutputStream emptyOs = new ByteArrayOutputStream();
private Exception ex;
private boolean closed = false;
AbstractFolder fo;
/** defines if should be fired fileChanged event after close of stream */
private boolean fireFileChanged;
public NotifyOutputStream(AbstractFolder fo, boolean fireFileChanged) {
super(emptyOs);
this.fo = fo;
if (annotateUnclosedStreams()) {
ex = new Exception();
}
this.fireFileChanged = fireFileChanged;
}
private void setOriginal(OutputStream os) {
out = os;
}
/** Faster implementation of writing than is implemented in
* the filter output stream.
*/
@Override
public void write(byte[] b, int off, int len) throws IOException {
out.write(b, off, len);
}
@Override
public void close() throws IOException {
if (!closed) {
closed = true;
ex = null;
try {
super.out.flush();
super.close();
} finally {
closeOutputStream(fo, this, fireFileChanged);
MIMESupport.freeCaches();
synchronized (StreamPool.class) {
StreamPool.class.notifyAll();
}
}
}
}
public Exception getException() {
return ex;
}
}
private static final class NotifyInputStream extends FilterInputStream {
private static final InputStream emptyIs = new ByteArrayInputStream(new byte[0]);
private Exception ex;
AbstractFolder fo;
private boolean closed = false;
public NotifyInputStream(AbstractFolder fo) {
super(emptyIs);
this.fo = fo;
if (annotateUnclosedStreams()) {
ex = new Exception();
}
}
private void setOriginal(InputStream is) {
in = is;
}
@Override
public void close() throws IOException {
if (!closed) {
closed = true;
ex = null;
super.close();
closeInputStream(fo, this);
synchronized (StreamPool.class) {
if (!StreamPool.get(fo).isInputStreamOpen()) {
StreamPool.class.notifyAll();
}
}
}
}
public Exception getException() {
return ex;
}
}
/** Whether to keep the stack traces. By default, don't - too expensive. */
private static boolean annotateUnclosedStreams() {
if (annotateUnclosedStreams != null) {
return annotateUnclosedStreams;
}
String annotateProp = System.getProperty("org.openide.filesystems.annotateUnclosedStreams"); // NOI18N;
annotateUnclosedStreams = Boolean.parseBoolean(annotateProp);
boolean assertsOn = false;
assert assertsOn = true;
if (assertsOn) {
annotateUnclosedStreams = annotateProp == null ? true : ! Boolean.FALSE.toString().equals(annotateProp);
}
return annotateUnclosedStreams;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy