1 /* Copyright (c) 2009 Peter Troshin
\r
3 * JAva Bioinformatics Analysis Web Services (JABAWS) @version: 1.0
\r
5 * This library is free software; you can redistribute it and/or modify it under the terms of the
\r
6 * Apache License version 2 as published by the Apache Software Foundation
\r
8 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
\r
9 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the Apache
\r
10 * License for more details.
\r
12 * A copy of the license is in apache_license.txt. It is also available here:
\r
13 * @see: http://www.apache.org/licenses/LICENSE-2.0.txt
\r
15 * Any republication or derived work distributed in source code form
\r
16 * must include this copyright and license notice.
\r
19 package compbio.engine;
\r
21 import java.io.File;
\r
22 import java.io.IOException;
\r
23 import java.io.UnsupportedEncodingException;
\r
24 import java.util.concurrent.Delayed;
\r
25 import java.util.concurrent.TimeUnit;
\r
27 import org.apache.log4j.Logger;
\r
29 import compbio.engine.client.PathValidator;
\r
30 import compbio.metadata.ChunkHolder;
\r
31 import compbio.util.FileWatcher;
\r
33 public class FilePuller implements Delayed {
\r
35 private static final Logger log = Logger.getLogger(FilePuller.class);
\r
37 // 5 minutes in nanosec
\r
38 private static long defaultDelay = 1000 * 1000 * 1000L * 5 * 60;
\r
39 private final File file;
\r
40 private long lastAccessTime;
\r
41 private FileWatcher watcher;
\r
42 final int chunkSize;
\r
44 // used for testing only
\r
47 private FilePuller(String file, int size) {
\r
48 FileWatcher.validateInput(file, size);
\r
49 if (compbio.util.Util.isEmpty(file)) {
\r
50 throw new NullPointerException("File name must be provided!");
\r
52 // The fact that the file may not exist at a time does not matter here
\r
53 if (!PathValidator.isAbsolutePath(file)) {
\r
54 throw new IllegalArgumentException("Absolute path to the File "
\r
55 + file + " is expected but not provided!");
\r
57 this.file = new File(file);
\r
58 this.chunkSize = size;
\r
59 this.lastAccessTime = System.nanoTime();
\r
62 private FilePuller(String file) {
\r
63 if (compbio.util.Util.isEmpty(file)) {
\r
64 throw new NullPointerException("File name must be provided!");
\r
66 // The fact that the file may not exist at a time does not matter here
\r
67 if (!PathValidator.isAbsolutePath(file)) {
\r
68 throw new IllegalArgumentException("Absolute path to the File "
\r
69 + file + " is expected but not provided!");
\r
71 this.file = new File(file);
\r
73 this.lastAccessTime = System.nanoTime();
\r
76 public static FilePuller newFilePuller(String file, int chunkSize) {
\r
77 return new FilePuller(file, chunkSize);
\r
81 * Progress Puller is designed to read 3 characters from the beginning of
\r
82 * the file, nothing more. Intended to be used in conjunction with a tool
\r
83 * which output progress as a percent value from 0 to 100. In any cases
\r
84 * progress could not be more than the largest byte value 255.
\r
89 public static FilePuller newProgressPuller(String file) {
\r
90 return new FilePuller(file);
\r
93 public ChunkHolder pull(long position) throws IOException {
\r
95 String valueUTF16 = watcher.pull(position);
\r
96 String value = null;
\r
97 if (valueUTF16 != null) {
\r
98 value = removeInvalidXMLCharacters(valueUTF16);
\r
100 return new ChunkHolder(value, watcher.getCursorPosition());
\r
104 * This method ensures that the output String has only valid XML unicode
\r
105 * characters as specified by the XML 1.0 standard. For reference, please
\r
106 * see the standard.
\r
109 * String whose non-valid characters we want to remove.
\r
111 * @return The in String, stripped of non-valid characters.
\r
113 static String removeInvalidXMLCharacters(String str) {
\r
114 assert str != null;
\r
116 StringBuilder out = new StringBuilder(); // Used to hold the output.
\r
117 int codePoint; // Used to reference the current character.
\r
120 // String ss = "\ud801\udc00"; // This is actualy one unicode character,
\r
121 // represented by two code units!!!.
\r
122 // System.out.println(ss.codePointCount(0, ss.length()));// See: 1
\r
124 String value = null;
\r
126 // make sure the string contain only UTF-8 characters
\r
127 value = new String(str.getBytes("UTF-8"), "UTF-8");
\r
128 } catch (UnsupportedEncodingException e) {
\r
130 throw new AssertionError("UTF-8 charset is not supported!!!");
\r
132 while (i < value.length()) {
\r
133 codePoint = value.codePointAt(i); // This is the unicode code of the
\r
135 if ((codePoint == 0x9)
\r
136 || // Consider testing larger ranges first to
\r
138 (codePoint == 0xA) || (codePoint == 0xD)
\r
139 || ((codePoint >= 0x20) && (codePoint <= 0xD7FF))
\r
140 || ((codePoint >= 0xE000) && (codePoint <= 0xFFFD))
\r
141 || ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF))) {
\r
143 out.append(Character.toChars(codePoint));
\r
146 i += Character.charCount(codePoint);
\r
148 * Increment with the number of code units(java chars) needed to
\r
149 * represent a Unicode char.
\r
152 return out.toString();
\r
155 public void initPull() {
\r
156 this.lastAccessTime = System.nanoTime();
\r
157 if (!isFileCreated()) {
\r
158 throw new IllegalStateException("File " + file.getAbsolutePath()
\r
159 + " has not been created yet! Cannot pull.");
\r
161 if (watcher == null) {
\r
166 public String getFile() {
\r
167 return file.getAbsolutePath();
\r
170 public boolean isFileCreated() {
\r
171 this.lastAccessTime = System.nanoTime();
\r
172 return file.exists();
\r
175 public void waitForFile(long maxWaitSeconds) {
\r
179 if (isFileCreated()) {
\r
183 Thread.sleep(step);
\r
184 // TODO is this needed? this.lastAccessTime = System.nanoTime();
\r
185 } catch (InterruptedException e) {
\r
186 // Propagate interruption up the stack trace for anyone
\r
188 Thread.currentThread().interrupt();
\r
189 log.debug("Thread interruped during waiting for file "
\r
190 + file.getAbsolutePath() + " to be created. Message: "
\r
195 if (waited / 1000 >= maxWaitSeconds) {
\r
201 public boolean hasMoreData() throws IOException {
\r
202 this.lastAccessTime = System.nanoTime();
\r
203 if (!isFileCreated()) {
\r
204 throw new IllegalStateException("File " + file.getAbsolutePath()
\r
205 + " has not been created yet! Cannot pull.");
\r
207 if (watcher == null) {
\r
210 return watcher.hasMore();
\r
213 private synchronized void init() {
\r
214 // Check watcher==null is duplicated to avoid adding synchronization to
\r
216 if (watcher == null) {
\r
217 if (chunkSize < FileWatcher.MIN_CHUNK_SIZE_BYTES) {
\r
218 watcher = FileWatcher
\r
219 .newProgressWatcher(file.getAbsolutePath());
\r
220 log.debug("Init Progress watcher with file: "
\r
221 + file.getAbsolutePath());
\r
223 watcher = FileWatcher.newFileWatcher(file.getAbsolutePath(),
\r
225 log.debug("Init File watcher with file: "
\r
226 + file.getAbsolutePath());
\r
233 public int compareTo(Delayed o) {
\r
234 return new Long(this.getDelay(TimeUnit.NANOSECONDS)).compareTo(o
\r
235 .getDelay(TimeUnit.NANOSECONDS));
\r
239 * This must return remaining delay associated with the object!
\r
242 * @see java.util.concurrent.Delayed#getDelay(java.util.concurrent.TimeUnit)
\r
245 public long getDelay(final TimeUnit unit) {
\r
246 long idleTime = System.nanoTime() - lastAccessTime;
\r
247 long delayVal = (delay == 0 ? defaultDelay : delay);
\r
248 return unit.convert(delayVal - idleTime, TimeUnit.NANOSECONDS);
\r
251 void setDelay(long delay, TimeUnit unit) {
\r
253 this.delay = TimeUnit.NANOSECONDS.convert(delay, unit);
\r
254 assert delay < defaultDelay;
\r
257 long getDelayValue(TimeUnit unit) {
\r
258 return (delay == 0 ? unit.convert(defaultDelay, TimeUnit.NANOSECONDS)
\r
259 : unit.convert(delay, TimeUnit.NANOSECONDS));
\r
262 public void disconnect() {
\r
263 synchronized (this) {
\r
264 if (watcher != null) {
\r
265 watcher.disconnect();
\r
271 boolean disconnected() {
\r
272 return watcher == null;
\r
276 public String toString() {
\r
277 String value = "File: " + this.file.getAbsolutePath() + "\n";
\r
278 value += "Delay (s): " + getDelayValue(TimeUnit.SECONDS) + "\n";
\r
279 long exp = getDelay(TimeUnit.MILLISECONDS);
\r
281 value += "Expire in (ms): " + exp + "\n";
\r
283 value += "This object has expired" + "\n";
\r
285 value += "ChunkSize " + this.chunkSize + "\n";
\r
290 public boolean equals(Object obj) {
\r
294 if (!(obj instanceof FilePuller)) {
\r
297 FilePuller fp = (FilePuller) obj;
\r
298 if (!this.file.equals(fp.file)) {
\r
301 // Other fields does not matter
\r
306 public int hashCode() {
\r
307 return this.file.hashCode();
\r
310 public byte getProgress() throws IOException {
\r
312 return watcher.getProgress();
\r