import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;

public class ReaderMain {

	public static void main(String[] args) throws IOException {
		File f = new File("temp");
		f.createNewFile();
		f.deleteOnExit();
		
		FileInputStream fsin = new FileInputStream(f);
		Reader in = new InputStreamReader(fsin);
		BufferedReader bufferedReader = new BufferedReader(in);
		
		//fsin.close(); this would not be detected
		//(c.f. tainted string example)
		in.close();
		while(true) {
			int content = bufferedReader.read();
			if(content<0) break;			
		}
		
	}
	
	static aspect ReaderChecker {
		
		/*
		 * This aspect contains two tracematches.
		 * The first one checks for each reader "r" which is created for an input stream "is", that
		 * neither "r" nor "is" are closed before one reads from "r" or "is".
		 * 
		 * The second tracematch checks the same for a reader that is created for another reader.
		 * 
		 * Unfortunately this does not yet capture the case where we create a reader "r1" on top of a reader
		 * "r2", which was created on top of a stream "is" and then we close "is" and read from "r2".
		 * (c.f. tainted string example for PQL)
		 */
		
		pointcut createReader(InputStream is):
			call(Reader+.new(InputStream+,..)) && args(is,..) ;
			
		tracematch(Reader r, InputStream is) {
			
			sym create after returning(r): createReader(is);
			
			sym closeR before: call(* close()) && target(r);
			sym closeI before: call(* close()) && target(is);
			
			sym readR before: (call(* read(..)) || call(* readLine(..))) && target(r);
			sym readI before: (call(* read(..)) || call(* readLine(..))) && target(is);
			
			create (closeR|closeI) (readR|readI) {
				throw new IllegalStateException("stream or reader closed");			
			}
			
		}

		pointcut createReaderFromReader(Reader r):
			call(Reader+.new(Reader+,..)) && args(r,..) ;

		tracematch(Reader r, Reader inner) {
			
			sym create after returning(r): createReaderFromReader(inner);
			
			sym closeR before: call(* close()) && target(r);
			sym closeI before: call(* close()) && target(inner);
			
			sym readR before: (call(* read(..)) || call(* readLine(..))) && target(r);
			sym readI before: (call(* read(..)) || call(* readLine(..))) && target(inner);
			
			create (closeR|closeI) (readR|readI) {
				throw new IllegalStateException("stream or reader closed");			
			}
			
		}
	}
}
