import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;

import javax.lang.model.element.Modifier;
import javax.lang.model.element.NestingKind;
import javax.tools.FileObject;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.ToolProvider;

class Ideone {
	public static void main(String[] args) throws Exception {
		String name="Test";
		String src="public class Test {\r\n" + 
				"  public static void main(String[] args) {\r\n" + 
				"    System.out.println(\"Hello \"+args[0]+\"!\");\r\n" + 
				"  }\r\n" + 
				"}";
		System.out.println(src);
		List<JFO> sources=Arrays.asList(new JFO(name,src));
		Map<String,JFO> files=new HashMap<>();
		JavaCompiler jc=ToolProvider.getSystemJavaCompiler();
		JavaCompiler.CompilationTask ct=jc.getTask(null, new JFM(jc.getStandardFileManager(null, null, null),files), null, null, null, sources);
		System.out.println(ct.call());
		Class<?> clazz=new CL().defineClass(name, files.get(name).baos.toByteArray());
		clazz.getDeclaredMethod("main", String[].class).invoke(null, (Object)new String[] {"StackOverflow"});
	}
	
	static class JFO implements JavaFileObject {
		final String name;
		final String src;
		JFO(String name,String src){
			this.name=name;
			this.src=src;
		}
		@Override
		public URI toUri() {
			URI uri=null;
			try {
				uri=new URI(name);
			}catch(URISyntaxException use) {use.printStackTrace();};
			System.out.println("toUri "+uri);
			return uri;
		}

		@Override
		public String getName() {
			System.out.println("getName "+name);
			return name;
		}

		@Override
		public InputStream openInputStream() throws IOException {
			throw new Error();
		}

		ByteArrayOutputStream baos;
		@Override
		public OutputStream openOutputStream() throws IOException {
			System.out.println("openOutputStream");
			if(baos!=null)throw new Error();
			baos=new ByteArrayOutputStream();
			return baos;
		}

		@Override
		public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
			throw new Error();
		}

		@Override
		public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
			System.out.println("getCharContent");
			return src;
		}

		@Override
		public Writer openWriter() throws IOException {
			throw new Error();
		}

		@Override
		public long getLastModified() {
			throw new Error();
		}

		@Override
		public boolean delete() {
			throw new Error();
		}

		@Override
		public Kind getKind() {
			System.out.println("getKind");
			return Kind.SOURCE;
		}

		@Override
		public boolean isNameCompatible(String simpleName, Kind kind) {
			System.out.println("isNameCompatible "+name+" ? "+simpleName);
			return name.equals(simpleName);
		}

		@Override
		public NestingKind getNestingKind() {
			throw new Error();
		}

		@Override
		public Modifier getAccessLevel() {
			throw new Error();
		}
		
	}
	
	static class JFM implements JavaFileManager {
		final JavaFileManager jfm;
		final Map<String, JFO> files;
		JFM(JavaFileManager jfm, Map<String, JFO> files){
			this.jfm=jfm;
			this.files=files;
		}

		@Override
		public int isSupportedOption(String option) {
			int iso=jfm.isSupportedOption(option);
			System.out.println("isSupportedOption "+option+" "+iso);
			return iso;
		}

		@Override
		public ClassLoader getClassLoader(Location location) {
			ClassLoader cl=jfm.getClassLoader(location);
			System.out.println("getClassLoader "+location+" "+cl);
			return cl;
		}

		@Override
		public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse)
				throws IOException {
			System.out.println("list "+location+" "+packageName+" ... "+recurse);
			return jfm.list(location, packageName, kinds, recurse);
		}

		@Override
		public String inferBinaryName(Location location, JavaFileObject file) {
			String ibm=jfm.inferBinaryName(location, file);
//			System.out.println("inferBinaryName "+location+" "+file+" "+ibm);
			return ibm;
		}

		@Override
		public boolean isSameFile(FileObject a, FileObject b) {
			throw new Error();
		}

		@Override
		public boolean handleOption(String current, Iterator<String> remaining) {
			boolean ho=jfm.handleOption(current, remaining);
			System.out.println("handleOption "+current+" ...");
			return ho;
		}

		@Override
		public boolean hasLocation(Location location) {
			boolean hl=jfm.hasLocation(location);
//			System.out.println("hasLocation "+location+" "+hl);
			return hl;
		}

		@Override
		public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException {
//			System.out.println("getJavaFileForInput "+location+" "+className+" "+kind.name());
			return jfm.getJavaFileForInput(location, className, kind);
		}

		@Override
		public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling)
				throws IOException {
			System.out.println("getJavaFileForOutput "+location+" "+className+" "+kind.name()+" "+sibling);
			if(files.containsKey(className))
				throw new Error();
			JFO jfo=new JFO(className,null);
			files.put(className, jfo);
			return jfo;
		}

		@Override
		public FileObject getFileForInput(Location location, String packageName, String relativeName)
				throws IOException {
			throw new Error();
		}

		@Override
		public FileObject getFileForOutput(Location location, String packageName, String relativeName,
				FileObject sibling) throws IOException {
			throw new Error();
		}

		@Override
		public void flush() throws IOException {
			System.out.println("flush");
		}

		@Override
		public void close() throws IOException {
			throw new Error();
		}
		
	    public Location getLocationForModule(Location location, String moduleName) throws IOException {
	        throw new UnsupportedOperationException();
	    }

	    public Location getLocationForModule(Location location, JavaFileObject fo) throws IOException {
	        throw new UnsupportedOperationException();
	    }

	    public <S> ServiceLoader<S> getServiceLoader(Location location, Class<S> service) throws  IOException {
	        throw new UnsupportedOperationException();
	    }

	    public String inferModuleName(Location location) throws IOException {
	    	String imm=jfm.inferModuleName(location);
//	    	System.out.println("inferModuleName "+location+" "+imm);
	    	return imm;
	    }

	    public Iterable<Set<Location>> listLocationsForModules(Location location) throws IOException {
	    	Iterable<Set<Location>> llfm=jfm.listLocationsForModules(location);
	    	System.out.println("listLocationsForModules "+location+" ...");
	    	return llfm;
	    }

	    public boolean contains(Location location, FileObject fo) throws IOException {
	        throw new UnsupportedOperationException();
	    }
	}
	
	static class CL extends ClassLoader {
		public Class<?> defineClass(String name,byte array[]) {
			return defineClass(name, array, 0, array.length);
		}
	}
}
