View Javadoc
1   /*
2    * Copyright (C) 2007 The Guava Authors
3    * Copyright (C) 2014, Sasa Zivkov <sasa.zivkov@sap.com>, SAP AG and others
4    *
5    * This program and the accompanying materials are made available under the
6    * terms of the Eclipse Distribution License v. 1.0 which is available at
7    * https://www.eclipse.org/org/documents/edl-v10.php.
8    *
9    * SPDX-License-Identifier: BSD-3-Clause
10   */
11  
12  package org.eclipse.jgit.util.io;
13  
14  import java.io.FilterInputStream;
15  import java.io.IOException;
16  import java.io.InputStream;
17  
18  import org.eclipse.jgit.internal.JGitText;
19  
20  /**
21   * Wraps a {@link java.io.InputStream}, limiting the number of bytes which can
22   * be read.
23   *
24   * This class was copied and modifed from the Google Guava 16.0. Differently
25   * from the original Guava code, when a caller tries to read from this stream
26   * past the given limit and the wrapped stream hasn't yet reached its EOF this
27   * class will call the limitExceeded method instead of returning EOF.
28   *
29   * @since 3.3
30   */
31  public abstract class LimitedInputStream extends FilterInputStream {
32  
33  	private long left;
34  	/** Max number of bytes to be read from the wrapped stream */
35  	protected final long limit;
36  	private long mark = -1;
37  
38  	/**
39  	 * Create a new LimitedInputStream
40  	 *
41  	 * @param in an InputStream
42  	 * @param limit max number of bytes to read from the InputStream
43  	 */
44  	protected LimitedInputStream(InputStream in, long limit) {
45  		super(in);
46  		left = limit;
47  		this.limit = limit;
48  	}
49  
50  	/** {@inheritDoc} */
51  	@Override
52  	public int available() throws IOException {
53  		return (int) Math.min(in.available(), left);
54  	}
55  
56  	// it's okay to mark even if mark isn't supported, as reset won't work
57  	/** {@inheritDoc} */
58  	@Override
59  	public synchronized void mark(int readLimit) {
60  		in.mark(readLimit);
61  		mark = left;
62  	}
63  
64  	/** {@inheritDoc} */
65  	@Override
66  	public int read() throws IOException {
67  		if (left == 0) {
68  			if (in.available() == 0) {
69  				return -1;
70  			}
71  			limitExceeded();
72  		}
73  
74  		int result = in.read();
75  		if (result != -1) {
76  			--left;
77  		}
78  		return result;
79  	}
80  
81  	/** {@inheritDoc} */
82  	@Override
83  	public int read(byte[] b, int off, int len) throws IOException {
84  		if (left == 0) {
85  			if (in.available() == 0) {
86  				return -1;
87  			}
88  			limitExceeded();
89  		}
90  
91  		len = (int) Math.min(len, left);
92  		int result = in.read(b, off, len);
93  		if (result != -1) {
94  			left -= result;
95  		}
96  		return result;
97  	}
98  
99  	/** {@inheritDoc} */
100 	@Override
101 	public synchronized void reset() throws IOException {
102 		if (!in.markSupported())
103 			throw new IOException(JGitText.get().unsupportedMark);
104 
105 		if (mark == -1)
106 			throw new IOException(JGitText.get().unsetMark);
107 
108 		in.reset();
109 		left = mark;
110 	}
111 
112 	/** {@inheritDoc} */
113 	@Override
114 	public long skip(long n) throws IOException {
115 		n = Math.min(n, left);
116 		long skipped = in.skip(n);
117 		left -= skipped;
118 		return skipped;
119 	}
120 
121 	/**
122 	 * Called when trying to read past the given {@link #limit} and the wrapped
123 	 * InputStream {@link #in} hasn't yet reached its EOF
124 	 *
125 	 * @throws java.io.IOException
126 	 *             subclasses can throw an {@link java.io.IOException} when the
127 	 *             limit is exceeded. The throws java.io.IOException will be
128 	 *             forwarded back to the caller of the read method which read
129 	 *             the stream past the limit.
130 	 */
131 	protected abstract void limitExceeded() throws IOException;
132 }