. 我已經將arc(android resource cleaner)的3.0 beta版本的源代碼發布到google code,項目目前還很粗鄙,需要進一步的完善,裡面也可能還有不少bug,需要解決,感興趣的朋友可以聯系我,裡面有我的qq號碼(286505491).
.項目裡面兩個主要的類:
ARC.java 主要負責UI和事件處理
[java]
package xiaogang.src;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.SwingUtilities;
class ARC extends JFrame {
private static final String VERSION_STR = "3.0 beta";
private static final long serialVersionUID = -1600532762007579488L;
private JMenu mFileMenu;
private JMenu mActionMenu;
private JMenu mHelpMenu;
private JMenuItem mOpenFileMenuItem;
private JMenuItem mDeleteMenuItem;
private JMenuItem mDeleteAllMenuItem;
private JMenuItem mExitMenuItem;
private JMenuItem mHelpMenuItem;
private JButton mJButton;
private JMenuBar mJMenuBar1;
private JList mUnusedResList;
private JSeparator mJSeparator1, mJSeparator2;
private ResProbe mCleanRes;
private String mSPath;
private boolean running = false;
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
ARC inst = new ARC();
inst.setLocationRelativeTo(null);
inst.setVisible(true);
}
});
}
public ARC() {
super();
setupGUI();
}
private void setupGUI() {
try {
BorderLayout thisLayout = new BorderLayout();
getContentPane().setLayout(thisLayout);
mUnusedResList = new JList();
mUnusedResList.setListData(new String[] {
"請打開Android工程根目錄"
});
mUnusedResList.setLayoutOrientation(JList.VERTICAL);
JScrollPane listScroller = new JScrollPane(mUnusedResList);
getContentPane().add(listScroller, BorderLayout.CENTER);
mJButton = new JButton("開始");
mJButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
if (running) {
mCleanRes.setbCancel(true);
mJButton.setText("開始");
running = false;
return;
}
if (mSPath == null && mSPath.length() <= 0) {
JOptionPane.showMessageDialog(ARC.this, "選擇目錄", "Error",
JOptionPane.ERROR_MESSAGE);
}
running = true;
mJButton.setText("取消");
new Thread() {
@Override
public void run() {
mCleanRes.run(ARC.this);
}
}.start();
}
});
getContentPane().add(mJButton, BorderLayout.SOUTH);
setSize(400, 300);
{
mJMenuBar1 = new JMenuBar();
setJMenuBar(mJMenuBar1);
{
mFileMenu = new JMenu();
mJMenuBar1.add(mFileMenu);
mFileMenu.setText("文件");
{
mOpenFileMenuItem = new JMenuItem();
mFileMenu.add(mOpenFileMenuItem);
mOpenFileMenuItem.setText("打開");
mOpenFileMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("打開")) {
JFileChooser jFileChooser = new JFileChooser();
jFileChooser
.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int returnVal = jFileChooser.showOpenDialog(ARC.this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
mSPath = jFileChooser.getSelectedFile().getAbsolutePath();
mCleanRes = new ResProbe(mSPath);
mUnusedResList.setListData(new String[] {
"工程目錄: "+mSPath
});
mUnusedResList.invalidate();
}
}
}
});
}
{
mJSeparator1 = new JSeparator();
mFileMenu.add(mJSeparator1);
}
{
mExitMenuItem = new JMenuItem();
mFileMenu.add(mExitMenuItem);
mExitMenuItem.setText("退出");
mExitMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
ARC.this.dispose();
}
});
}
}
mActionMenu = new JMenu();
mJMenuBar1.add(mActionMenu);
mActionMenu.setText("操作");
mDeleteMenuItem = new JMenuItem();
mActionMenu.add(mDeleteMenuItem);
mDeleteMenuItem.setText("刪除");
mDeleteMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
mCleanRes.delete(mUnusedResList.getSelectedIndices());
mUnusedResList.setListData(mCleanRes.getResult());
mUnusedResList.invalidate();
}
});
mJSeparator2 = new JSeparator();
mActionMenu.add(mJSeparator2);
mDeleteAllMenuItem = new JMenuItem();
mActionMenu.add(mDeleteAllMenuItem);
mDeleteAllMenuItem.setText("刪除所有");
mDeleteAllMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
mCleanRes.deleteAll();
mUnusedResList.setListData(mCleanRes.getResult());
mUnusedResList.invalidate();
}
});
mHelpMenu = new JMenu();
mJMenuBar1.add(mHelpMenu);
mHelpMenu.setText("幫助");
mHelpMenuItem = new JMenuItem();
mHelpMenu.add(mHelpMenuItem);
mHelpMenuItem.setText("幫助");
mHelpMenuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
JOptionPane.showMessageDialog(ARC.this, "Version v" + VERSION_STR
+ "\n趙小剛, QQ: 286505491", "ARC",
JOptionPane.INFORMATION_MESSAGE);
}
});
}
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
} catch (Exception e) {
e.printStackTrace();
}
}
public void setResult(List<ResSet> list) {
running = false;
mJButton.setText("開始");
String[] fileList = new String[list.size()];
for (int i = 0; i < list.size(); i++) {
fileList[i] = list.get(i).toString();
}
if (fileList.length == 0) {
fileList = new String[1];
fileList[0] = "No result";
}
mUnusedResList.setListData(fileList);
mUnusedResList.invalidate();
}
public void setProgress(File file) {
String[] pathList = new String[3];
pathList[0] = "正在處理... : " + mSPath;
pathList[1] = file.getParent().replace(mSPath, "");
pathList[2] = file.getName();
mUnusedResList.setListData(pathList);
mUnusedResList.invalidate();
}
}
ResProbe.java 負責掃描各種資源文件,源代碼,r文件,清單文件,然後經過對比,找出冗余資源的核心類
[java]
package xiaogang.src;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ResProbe {
private volatile boolean isCanceled = false;
private String mPackageName;
private ARC mCallback;
private File mBaseDirectory;
private File mSrcDirectory;
private File mResDirectory;
private File mGenDirectory;
private File mManifestFile;
private File mRJavaFile;
private List<ResSet> mResList = new ArrayList<ResSet>();
private final Set<ResSet> mResources = new HashSet<ResSet>();
private final Set<ResSet> mUsedResources = new HashSet<ResSet>();
private static final Pattern sResourceTypePattern = Pattern
.compile("^\\s*public static final class (\\w+)\\s*\\{$");
private static final Pattern sResourceNamePattern = Pattern
.compile("^\\s*public static( final)? int(\\[\\])? (\\w+)\\s*=\\s*(\\{|(0x)?[0-9A-Fa-f]+;)\\s*$");
private static final FileType sJavaFileType = new FileType("java", "R." + FileType.USAGE_TYPE
+ "." + FileType.USAGE_NAME + "[^\\w_]");
private static final FileType sXmlFileType = new FileType("xml", "[\" >]@"
+ FileType.USAGE_TYPE + "/" + FileType.USAGE_NAME + "[\" <]");
private static final Map<String, ResType> sResourceTypes = new HashMap<String, ResType>();
static {
// anim
sResourceTypes.put("anim", new ResType("anim") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals(getType())) {
return false;
}
final String name = fileName.split("\\.")[0];
final Pattern pattern = Pattern.compile("^" + resourceName + "$");
return pattern.matcher(name).find();
}
});
// array
sResourceTypes.put("array", new ResType("array") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("values")) {
return false;
}
final Pattern pattern = Pattern.compile("<([a-z]+\\-)?array.*?name\\s*=\\s*\""
+ resourceName + "\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
return false;
}
});
// attr
sResourceTypes.put("attr", new ResType("attr") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("values")) {
return false;
}
final Pattern pattern = Pattern.compile("<attr.*?name\\s*=\\s*\"" + resourceName
+ "\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
return false;
}
@Override
public boolean doesFileUseResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (parent != null) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("layout") && !directoryType.equals("values")) {
return false;
}
}
final Pattern pattern = Pattern.compile("<.+?:" + resourceName
+ "\\s*=\\s*\".*?\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
final Pattern itemPattern = Pattern.compile("<item.+?name\\s*=\\s*\""
+ resourceName + "\".*?>");
final Matcher itemMatcher = itemPattern.matcher(fileContents);
if (itemMatcher.find()) {
return true;
}
return false;
}
});
// bool
sResourceTypes.put("bool", new ResType("bool") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("values")) {
return false;
}
final Pattern pattern = Pattern.compile("<bool.*?name\\s*=\\s*\"" + resourceName
+ "\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
return false;
}
});
// color
sResourceTypes.put("color", new ResType("color") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("values")) {
return false;
}
final Pattern pattern = Pattern.compile("<color.*?name\\s*=\\s*\"" + resourceName
+ "\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
return false;
}
});
// dimen
sResourceTypes.put("dimen", new ResType("dimen") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("values")) {
return false;
}
final Pattern pattern = Pattern.compile("<dimen.*?name\\s*=\\s*\"" + resourceName
+ "\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
return false;
}
});
// drawable
sResourceTypes.put("drawable", new ResType("drawable") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (directoryType.equals(getType())) {
final String name = fileName.split("\\.")[0];
final Pattern pattern = Pattern.compile("^" + resourceName + "$");
return pattern.matcher(name).find();
}
if (directoryType.equals("values")) {
final Pattern pattern = Pattern.compile("<drawable.*?name\\s*=\\s*\""
+ resourceName + "\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
}
return false;
}
});
// id
sResourceTypes.put("id", new ResType("id") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("values") && !directoryType.equals("layout")) {
return false;
}
final Pattern valuesPattern0 = Pattern
.compile("<item.*?type\\s*=\\s*\"id\".*?name\\s*=\\s*\"" + resourceName
+ "\".*?/?>");
final Pattern valuesPattern1 = Pattern.compile("<item.*?name\\s*=\\s*\""
+ resourceName + "\".*?type\\s*=\\s*\"id\".*?/?>");
final Pattern layoutPattern = Pattern.compile(":id\\s*=\\s*\"@\\+id/"
+ resourceName + "\"");
Matcher matcher = valuesPattern0.matcher(fileContents);
if (matcher.find()) {
return true;
}
matcher = valuesPattern1.matcher(fileContents);
if (matcher.find()) {
return true;
}
matcher = layoutPattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
return false;
}
});
// integer
sResourceTypes.put("integer", new ResType("integer") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("values")) {
return false;
}
final Pattern pattern = Pattern.compile("<integer.*?name\\s*=\\s*\"" + resourceName
+ "\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
return false;
}
});
// layout
sResourceTypes.put("layout", new ResType("layout") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals(getType())) {
return false;
}
final String name = fileName.split("\\.")[0];
final Pattern pattern = Pattern.compile("^" + resourceName + "$");
return pattern.matcher(name).find();
}
});
// menu
sResourceTypes.put("menu", new ResType("menu") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals(getType())) {
return false;
}
final String name = fileName.split("\\.")[0];
final Pattern pattern = Pattern.compile("^" + resourceName + "$");
return pattern.matcher(name).find();
}
});
// plurals
sResourceTypes.put("plurals", new ResType("plurals") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("values")) {
return false;
}
final Pattern pattern = Pattern.compile("<plurals.*?name\\s*=\\s*\"" + resourceName
+ "\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
return false;
}
});
// raw
sResourceTypes.put("raw", new ResType("raw") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals(getType())) {
return false;
}
final String name = fileName.split("\\.")[0];
final Pattern pattern = Pattern.compile("^" + resourceName + "$");
return pattern.matcher(name).find();
}
});
// string
sResourceTypes.put("string", new ResType("string") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("values")) {
return false;
}
final Pattern pattern = Pattern.compile("<string.*?name\\s*=\\s*\"" + resourceName
+ "\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
return false;
}
});
// style
sResourceTypes.put("style", new ResType("style") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("values")) {
return false;
}
final Pattern pattern = Pattern.compile("<style.*?name\\s*=\\s*\"" + resourceName
+ "\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
return false;
}
@Override
public boolean doesFileUseResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (parent != null) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("values")) {
return false;
}
}
// (name="Parent.Child")
final Pattern pattern = Pattern.compile("<style.*?name\\s*=\\s*\"" + resourceName
+ "\\.\\w+\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
// (parent="Parent")
final Pattern pattern1 = Pattern.compile("<style.*?parent\\s*=\\s*\""
+ resourceName + "\".*?/?>");
final Matcher matcher1 = pattern1.matcher(fileContents);
if (matcher1.find()) {
return true;
}
return false;
}
});
// styleable
sResourceTypes.put("styleable", new ResType("styleable") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals("values")) {
return false;
}
final String[] styleableAttr = resourceName.split("\\[_\\\\.\\]");
if (styleableAttr.length == 1) {
final Pattern pattern = Pattern.compile("<declare-styleable.*?name\\s*=\\s*\""
+ styleableAttr[0] + "\".*?/?>");
final Matcher matcher = pattern.matcher(fileContents);
if (matcher.find()) {
return true;
}
return false;
}
final Pattern blockPattern = Pattern.compile("<declare-styleable.*?name\\s*=\\s*\""
+ styleableAttr[0] + "\".*?>(.*?)</declare-styleable\\s*>");
final Matcher blockMatcher = blockPattern.matcher(fileContents);
if (blockMatcher.find()) {
final String styleableAttributes = blockMatcher.group(1);
final Pattern attributePattern = Pattern.compile("<attr.*?name\\s*=\\s*\""
+ styleableAttr[1] + "\".*?/?>");
final Matcher attributeMatcher = attributePattern.matcher(styleableAttributes);
if (attributeMatcher.find()) {
return true;
}
return false;
}
return false;
}
});
// xml
sResourceTypes.put("xml", new ResType("xml") {
@Override
public boolean doesFileDeclareResource(final File parent, final String fileName,
final String fileContents, final String resourceName) {
if (!parent.isDirectory()) {
return false;
}
final String directoryType = parent.getName().split("-")[0];
if (!directoryType.equals(getType())) {
return false;
}
final String name = fileName.split("\\.")[0];
final Pattern pattern = Pattern.compile("^" + resourceName + "$");
return pattern.matcher(name).find();
}
});
}
public ResProbe() {
super();
final String baseDirectory = System.getProperty("user.dir");
mBaseDirectory = new File(baseDirectory);
}
protected ResProbe(final String baseDirectory) {
super();
mBaseDirectory = new File(baseDirectory);
}
public void delete(int[] list) {
LinkedList<ResSet> remove = new LinkedList<ResSet>();
for (int i : list) {
ResSet result = mResList.get(i);
final String type = result.getType();
System.out.println("type: "+type);
if (type.equals("anim") || type.equals("drawable") || type.equals("layout")) {
final String path = result.getPath();
System.out.println("path: "+path);
if (!isEmpty(path)) {
File file = new File(path);
file.delete();
remove.add(result);
}
}
}
mResList.removeAll(remove);
}
public String[] getResult() {
String[] result = new String[mResList.size()];
for (int i = 0; i < mResList.size(); i++) {
result[i] = mResList.get(i).toString();
}
return result;
}
public void deleteAll() {
LinkedList<ResSet> remove = new LinkedList<ResSet>();
for (ResSet resource : mResList) {
final String type = resource.getType();
if (type.equals("anim") || type.equals("drawable") || type.equals("layout") ||type.equals("menu")) {
final String path = resource.getPath();
if (!isEmpty(path)) {
File file = new File(path);
file.delete();
remove.add(resource);
}
}
}
mResList.removeAll(remove);
}
private boolean isEmpty(CharSequence str) {
if (str == null || str.length() == 0)
return true;
else
return false;
}
public void showResult() {
if (mCallback != null) {
mCallback.setResult(mResList);
}
}
@Override
protected void finalize() throws Throwable {
if (mResList != null) {
mResList.clear();
}
super.finalize();
}
public void run(ARC callback) {
isCanceled = false;
if (callback != null) {
this.mCallback = callback;
}
System.out.println("Running in: " + mBaseDirectory.getAbsolutePath());
findPaths();
if (mSrcDirectory == null || mResDirectory == null || mManifestFile == null) {
System.err.println("The current directory is not a valid Android project root.");
return;
}
mPackageName = findPackageName(mManifestFile);
if (mPackageName == null || mPackageName.trim().length() == 0) {
return;
}
if (mGenDirectory == null) {
System.err.println("You must first build your project to generate R.java");
return;
}
mRJavaFile = findRJavaFile(mGenDirectory, mPackageName);
if (mRJavaFile == null) {
System.err.println("You must first build your project to generate R.java");
return;
}
mResources.clear();
try {
mResources.addAll(getResourceList(mRJavaFile));//查找R文件
} catch (final IOException e) {
System.err.println("The R.java found could not be opened.");
e.printStackTrace();
}
if (isCanceled) {
return;
}
System.out.println(mResources.size() + " resources found");
System.out.println();
mUsedResources.clear();
searchFiles(null, mSrcDirectory, sJavaFileType);
if (isCanceled) {
return;
}
searchFiles(null, mResDirectory, sXmlFileType);
if (isCanceled) {
return;
}
searchFiles(null, mManifestFile, sXmlFileType);
if (isCanceled) {
return;
}
/*
* Because attr and styleable are so closely linked, we need to do some
* matching now to ensure we don't say an attr is unused if its
* corresponding styleable is used.
*/
final Set<ResSet> extraUsedResources = new HashSet<ResSet>();
for (final ResSet resource : mResources) {
if (resource.getType().equals("styleable")) {
final String[] styleableAttr = resource.getName().split("_");
if (styleableAttr.length > 1) {
final String attrName = styleableAttr[1];
final ResSet attrResourceTest = new ResSet("attr", attrName);
if (mUsedResources.contains(attrResourceTest)) {
extraUsedResources.add(resource);
}
}
} else if (resource.getType().equals("attr")) {
for (final ResSet usedResource : mUsedResources) {
if (usedResource.getType().equals("styleable")) {
final String[] styleableAttr = usedResource.getName().split("_");
if (styleableAttr.length > 1 && styleableAttr[1].equals(resource.getName())) {
extraUsedResources.add(resource);
}
}
}
}
}
for (final ResSet resource : extraUsedResources) {
mResources.remove(resource);
mUsedResources.add(resource);
}
final SortedMap<String, SortedMap<String, ResSet>> unusedResources = new TreeMap<String, SortedMap<String, ResSet>>();
for (final ResSet resource : mResources) {
final String type = resource.getType();
SortedMap<String, ResSet> typeMap = unusedResources.get(type);
if (typeMap == null) {
typeMap = new TreeMap<String, ResSet>();
unusedResources.put(type, typeMap);
}
typeMap.put(resource.getName(), resource);
}
final Map<String, ResType> unusedResourceTypes = new HashMap<String, ResType>(
unusedResources.size());
for (final String type : unusedResources.keySet()) {
final ResType resourceType = sResourceTypes.get(type);
if (resourceType != null) {
unusedResourceTypes.put(type, resourceType);
}
}
findDeclaredPaths(null, mResDirectory, unusedResourceTypes, unusedResources);
/*
* Find the paths where the used resources are declared.
*/
final SortedMap<String, SortedMap<String, ResSet>> usedResources = new TreeMap<String, SortedMap<String, ResSet>>();
for (final ResSet resource : mUsedResources) {
final String type = resource.getType();
SortedMap<String, ResSet> typeMap = usedResources.get(type);
if (typeMap == null) {
typeMap = new TreeMap<String, ResSet>();
usedResources.put(type, typeMap);
}
typeMap.put(resource.getName(), resource);
}
if (isCanceled) {
return;
}
final Map<String, ResType> usedResourceTypes = new HashMap<String, ResType>(
usedResources.size());
for (final String type : usedResources.keySet()) {
final ResType resourceType = sResourceTypes.get(type);
if (resourceType != null) {
usedResourceTypes.put(type, resourceType);
}
}
if (isCanceled) {
return;
}
findDeclaredPaths(null, mResDirectory, usedResourceTypes, usedResources);
final Set<ResSet> libraryProjectResources = getLibraryProjectResources();
for (final ResSet libraryResource : libraryProjectResources) {
final SortedMap<String, ResSet> typedResources = unusedResources.get(libraryResource
.getType());
if (typedResources != null) {
final ResSet appResource = typedResources.get(libraryResource.getName());
if (appResource != null && appResource.hasNoDeclaredPaths()) {
typedResources.remove(libraryResource.getName());
mResources.remove(appResource);
}
}
}
if (isCanceled) {
return;
}
final int unusedResourceCount = mResources.size();
if (unusedResourceCount > 0) {
System.out.println(unusedResourceCount + " unused resources were found:");
final SortedSet<ResSet> sortedResources = new TreeSet<ResSet>(mResources);
for (final ResSet resource : sortedResources) {
System.out.println(resource);
}
mResList.addAll(sortedResources);
}
showResult();
}
private void findPaths() {
final File[] children = mBaseDirectory.listFiles();
if (children == null) {
return;
}
for (final File file : children) {
if (file.isDirectory()) {
if (file.getName().equals("src")) {
mSrcDirectory = file;
} else if (file.getName().equals("res")) {
mResDirectory = file;
} else if (file.getName().equals("gen")) {
mGenDirectory = file;
}
} else if (file.getName().equals("AndroidManifest.xml")) {
mManifestFile = file;
}
}
}
private static String findPackageName(final File androidManifestFile) {
String manifest = "";
try {
manifest = FileUtils.getFileContents(androidManifestFile);
} catch (final IOException e) {
e.printStackTrace();
}
final Pattern pattern = Pattern
.compile("<manifest\\s+.*?package\\s*=\\s*\"([A-Za-z0-9_\\.]+)\".*?>");
final Matcher matcher = pattern.matcher(manifest);
if (matcher.find()) {
return matcher.group(1);
}
return null;
}
private static File findRJavaFile(final File baseDirectory, final String packageName) {
final File rJava = new File(baseDirectory, packageName.replace('.', '/') + "/R.java");
if (rJava.exists()) {
return rJava;
}
return null;
}
public void setbCancel(boolean bCancel) {
this.isCanceled = bCancel;
}
/**
* Removes all resources declared in library projects.
*/
private Set<ResSet> getLibraryProjectResources() {
final Set<ResSet> resources = new HashSet<ResSet>();
final File projectPropertiesFile = new File(mBaseDirectory, "project.properties");
if (!projectPropertiesFile.exists()) {
return resources;
}
List<String> fileLines = new ArrayList<String>();
try {
fileLines = FileUtils.getFileLines(projectPropertiesFile);
} catch (final IOException e) {
e.printStackTrace();
}
final Pattern libraryProjectPattern = Pattern.compile(
"^android\\.library\\.reference\\.\\d+=(.*)$", Pattern.CASE_INSENSITIVE);
final List<String> libraryProjectPaths = new ArrayList<String>();
for (final String line : fileLines) {
final Matcher libraryProjectMatcher = libraryProjectPattern.matcher(line);
if (libraryProjectMatcher.find()) {
libraryProjectPaths.add(libraryProjectMatcher.group(1));
}
}
for (final String libraryProjectPath : libraryProjectPaths) {
final File libraryProjectDirectory = new File(mBaseDirectory, libraryProjectPath);
if (libraryProjectDirectory.exists() && libraryProjectDirectory.isDirectory()) {
final String libraryProjectPackageName = findPackageName(new File(
libraryProjectDirectory, "AndroidManifest.xml"));
final File libraryProjectRJavaFile = findRJavaFile(new File(
libraryProjectDirectory, "gen"), libraryProjectPackageName);
if (libraryProjectRJavaFile != null) {
try {
resources.addAll(getResourceList(libraryProjectRJavaFile));
} catch (final IOException e) {
e.printStackTrace();
}
}
}
}
return resources;
}
private static Set<ResSet> getResourceList(final File rJavaFile) throws IOException {
final InputStream inputStream = new FileInputStream(rJavaFile);
final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
boolean done = false;
final Set<ResSet> resources = new HashSet<ResSet>();
String type = "";
while (!done) {
final String line = reader.readLine();
done = (line == null);
if (line != null) {
final Matcher typeMatcher = sResourceTypePattern.matcher(line);
final Matcher nameMatcher = sResourceNamePattern.matcher(line);
if (nameMatcher.find()) {
resources.add(new ResSet(type, nameMatcher.group(3)));
} else if (typeMatcher.find()) {
type = typeMatcher.group(1);
}
}
}
reader.close();
inputStream.close();
return resources;
}
private void searchFiles(final File parent, final File file, final FileType fileType) {
if (isCanceled) {
return;
}
if (file.isDirectory()) {
for (final File child : file.listFiles()) {
if (isCanceled) {
return;
}
searchFiles(file, child, fileType);
}
} else if (file.getName().endsWith(fileType.getExtension())) {
try {
if (isCanceled) {
return;
}
searchFile(parent, file, fileType);
if (mCallback != null) {
mCallback.setProgress(file);
}
} catch (final IOException e) {
System.err.println("There was a problem reading " + file.getAbsolutePath());
e.printStackTrace();
}
}
}
private void searchFile(final File parent, final File file, final FileType fileType)
throws IOException {
final Set<ResSet> foundResources = new HashSet<ResSet>();
final String fileContents = FileUtils.getFileContents(file);
for (final ResSet resource : mResources) {
if (isCanceled) {
return;
}
final Matcher matcher = fileType.getPattern(resource.getType(),
resource.getName().replace("_", "[_\\.]")).matcher(fileContents);
if (matcher.find()) {
foundResources.add(resource);
} else {
final ResType type = sResourceTypes.get(resource.getType());
if (type != null
&& type.doesFileUseResource(parent, file.getName(), fileContents, resource
.getName().replace("_", "[_\\.]"))) {
foundResources.add(resource);
}
}
}
for (final ResSet resource : foundResources) {
if (isCanceled) {
return;
}
mUsedResources.add(resource);
mResources.remove(resource);
}
}
private void findDeclaredPaths(final File parent, final File file,
final Map<String, ResType> resourceTypes,
final Map<String, SortedMap<String, ResSet>> resources) {
if (file.isDirectory()) {
for (final File child : file.listFiles()) {
if (isCanceled) {
return;
}
if (!child.isHidden()) {
findDeclaredPaths(file, child, resourceTypes, resources);
}
}
} else {
if (!file.isHidden()) {
final String fileName = file.getName();
String fileContents = "";
try {
fileContents = FileUtils.getFileContents(file);
} catch (final IOException e) {
e.printStackTrace();
}
for (final ResType resourceType : resourceTypes.values()) {
if (isCanceled) {
return;
}
final Map<String, ResSet> typeMap = resources.get(resourceType.getType());
if (typeMap != null) {
for (final ResSet resource : typeMap.values()) {
if (isCanceled) {
return;
}
if (resourceType.doesFileDeclareResource(parent, fileName,
fileContents, resource.getName().replace("_", "[_\\.]"))) {
resource.addDeclaredPath(file.getAbsolutePath());
final String configuration = parent.getName();
resource.addConfiguration(configuration);
}
}
}
}
}
}
}