blob: e27669f58d6a8e2f095ab7d291495a03d92ea176 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}
}
}