/******************************************************************************* * Copyright (c) 2006, 2014 BEA Systems, Inc. and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * wharley@bea.com - initial API and implementation * *******************************************************************************/ package org.eclipse.jdt.internal.compiler.apt.dispatch; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import javax.tools.ForwardingJavaFileObject; import javax.tools.JavaFileObject; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; import org.eclipse.jdt.internal.compiler.env.IBinaryType; import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding; import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; /** * A delegating JavaFileObject that hooks the close() methods of the Writer * or OutputStream objects that it produces, and notifies the annotation * dispatch manager when a new compilation unit is produced. */ public class HookedJavaFileObject extends ForwardingJavaFileObject<JavaFileObject> { // A delegating Writer that passes all commands to its contained Writer, // but hooks close() to notify the annotation dispatch manager of the new unit. private class ForwardingWriter extends Writer { private final Writer _w; ForwardingWriter(Writer w) { _w = w; } @Override public Writer append(char c) throws IOException { return _w.append(c); } @Override public Writer append(CharSequence csq, int start, int end) throws IOException { return _w.append(csq, start, end); } @Override public Writer append(CharSequence csq) throws IOException { return _w.append(csq); } // This is the only interesting method - it has to notify the // dispatch manager of the new file. @Override public void close() throws IOException { _w.close(); closed(); } @Override public void flush() throws IOException { _w.flush(); } @Override public void write(char[] cbuf) throws IOException { _w.write(cbuf); } @Override public void write(int c) throws IOException { _w.write(c); } @Override public void write(String str, int off, int len) throws IOException { _w.write(str, off, len); } @Override public void write(String str) throws IOException { _w.write(str); } @Override public void write(char[] cbuf, int off, int len) throws IOException { _w.write(cbuf, off, len); } @Override protected Object clone() throws CloneNotSupportedException { return new ForwardingWriter(this._w); } @Override public int hashCode() { return _w.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final ForwardingWriter other = (ForwardingWriter) obj; if (_w == null) { if (other._w != null) return false; } else if (!_w.equals(other._w)) return false; return true; } @Override public String toString() { return "ForwardingWriter wrapping " + _w.toString(); //$NON-NLS-1$ } } // A delegating Writer that passes all commands to its contained Writer, // but hooks close() to notify the annotation dispatch manager of the new unit. private class ForwardingOutputStream extends OutputStream { private final OutputStream _os; ForwardingOutputStream(OutputStream os) { _os = os; } @Override public void close() throws IOException { _os.close(); closed(); } @Override public void flush() throws IOException { _os.flush(); } @Override public void write(byte[] b, int off, int len) throws IOException { _os.write(b, off, len); } @Override public void write(byte[] b) throws IOException { _os.write(b); } @Override public void write(int b) throws IOException { _os.write(b); } @Override protected Object clone() throws CloneNotSupportedException { return new ForwardingOutputStream(this._os); } @Override public int hashCode() { return _os.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; final ForwardingOutputStream other = (ForwardingOutputStream) obj; if (_os == null) { if (other._os != null) return false; } else if (!_os.equals(other._os)) return false; return true; } @Override public String toString() { return "ForwardingOutputStream wrapping " + _os.toString(); //$NON-NLS-1$ } } /** * The Filer implementation that we need to notify when a new file is created. */ protected final BatchFilerImpl _filer; /** * The name of the file that is created; this is passed to the CompilationUnit constructor, * and ultimately to the java.io.File constructor, so it is a normal pathname, just like * what would be on the compiler command line. */ protected final String _fileName; /** * A compilation unit is created when the writer or stream is closed. Only do this once. */ private boolean _closed = false; private String _typeName; public HookedJavaFileObject(JavaFileObject fileObject, String fileName, String typeName, BatchFilerImpl filer) { super(fileObject); _filer = filer; _fileName = fileName; _typeName = typeName; } @Override public OutputStream openOutputStream() throws IOException { return new ForwardingOutputStream(super.openOutputStream()); } @Override public Writer openWriter() throws IOException { return new ForwardingWriter(super.openWriter()); } protected void closed() { if (!_closed) { _closed = true; //TODO: support encoding switch(this.getKind()) { case SOURCE : CompilationUnit unit = new CompilationUnit(null, _fileName, null /* encoding */); _filer.addNewUnit(unit); break; case CLASS : IBinaryType binaryType = null; try { binaryType = ClassFileReader.read(_fileName); } catch (ClassFormatException e) { /* When the annotation processor produces garbage, javac seems to show some resilience, by hooking the source type, which since is resolved can answer annotations during discovery - Not sure if this sanctioned by the spec, to be taken up with Oracle. Here we mimic the bug, see that addNewClassFile is simply collecting ReferenceBinding's, so adding a SourceTypeBinding works just fine. */ ReferenceBinding type = this._filer._env._compiler.lookupEnvironment.getType(CharOperation.splitOn('.', _typeName.toCharArray())); if (type != null) _filer.addNewClassFile(type); } catch (IOException e) { // ignore } if (binaryType != null) { char[] name = binaryType.getName(); ReferenceBinding type = this._filer._env._compiler.lookupEnvironment.getType(CharOperation.splitOn('/', name)); if (type != null && type.isValidBinding()) { if (type.isBinaryBinding()) { _filer.addNewClassFile(type); } else { BinaryTypeBinding binaryBinding = new BinaryTypeBinding(type.getPackage(), binaryType, this._filer._env._compiler.lookupEnvironment, true); if (binaryBinding != null) _filer.addNewClassFile(binaryBinding); } } } break; case HTML: case OTHER: break; } } } }