/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.build.gradle.tasks.annotations;
import static com.android.SdkConstants.DOT_CLASS;
import static org.objectweb.asm.Opcodes.ASM5;
import com.android.annotations.NonNull;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.io.Files;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;
/**
* Finds and deletes typedef annotation classes (and also warns if their
* retention is wrong, such that usages of the annotation embeds data
* into the .class file.)
* <p>
* (Based on the similar class in {@code development/tools/rmtypedefs/})
*/
@SuppressWarnings("SpellCheckingInspection")
public class TypedefRemover {
private final Extractor mExtractor;
private final boolean mQuiet;
private final boolean mVerbose;
private final boolean mDryRun;
public TypedefRemover(
@NonNull Extractor extractor,
boolean quiet,
boolean verbose,
boolean dryRun) {
mExtractor = extractor;
mQuiet = quiet;
mVerbose = verbose;
mDryRun = dryRun;
}
private Set<String> mAnnotationNames = Sets.newHashSet();
private List<File> mAnnotationClassFiles = Lists.newArrayList();
private Set<File> mAnnotationOuterClassFiles = Sets.newHashSet();
public void remove(@NonNull File classDir, @NonNull List<String> owners) {
if (!mQuiet) {
mExtractor.info("Deleting @IntDef and @StringDef annotation class files");
}
// Record typedef annotation names and files
for (String owner : owners) {
File file = new File(classDir, owner.replace('/', File.separatorChar) + DOT_CLASS);
addTypeDef(owner, file);
}
// Rewrite the .class files for any classes that *contain* typedefs as innerclasses
rewriteOuterClasses();
// Removes the actual .class files for the typedef annotations
deleteAnnotationClasses();
}
/**
* Records the given class name (internal name) and class file path as corresponding to a
* typedef annotation
* */
private void addTypeDef(String name, File file) {
mAnnotationClassFiles.add(file);
mAnnotationNames.add(name);
String fileName = file.getName();
int index = fileName.lastIndexOf('$');
if (index != -1) {
File parentFile = file.getParentFile();
assert parentFile != null : file;
File container = new File(parentFile, fileName.substring(0, index) + ".class");
if (container.exists()) {
mAnnotationOuterClassFiles.add(container);
} else {
Extractor.error("Warning: Could not find outer class " + container
+ " for typedef " + file);
}
}
}
/**
* Rewrites the outer classes containing the typedefs such that they no longer refer to
* the (now removed) typedef annotation inner classes
*/
private void rewriteOuterClasses() {
for (File file : mAnnotationOuterClassFiles) {
byte[] bytes;
try {
bytes = Files.toByteArray(file);
} catch (IOException e) {
Extractor.error("Could not read " + file + ": " + e.getLocalizedMessage());
continue;
}
ClassWriter classWriter = new ClassWriter(ASM5);
ClassVisitor classVisitor = new ClassVisitor(ASM5, classWriter) {
@Override
public void visitInnerClass(String name, String outerName, String innerName,
int access) {
if (!mAnnotationNames.contains(name)) {
super.visitInnerClass(name, outerName, innerName, access);
}
}
};
ClassReader reader = new ClassReader(bytes);
reader.accept(classVisitor, 0);
byte[] rewritten = classWriter.toByteArray();
try {
Files.write(rewritten, file);
} catch (IOException e) {
Extractor.error("Could not write " + file + ": " + e.getLocalizedMessage());
//noinspection UnnecessaryContinue
continue;
}
}
}
/**
* Performs the actual deletion (or display, if in dry-run mode) of the typedef annotation
* files
*/
private void deleteAnnotationClasses() {
for (File mFile : mAnnotationClassFiles) {
if (mVerbose) {
if (mDryRun) {
mExtractor.info("Would delete " + mFile);
} else {
mExtractor.info("Deleting " + mFile);
}
}
if (!mDryRun) {
boolean deleted = mFile.delete();
if (!deleted) {
Extractor.warning("Could not delete " + mFile);
}
}
}
}
}