mirror of
https://github.com/revanced/smali.git
synced 2025-06-12 12:17:37 +02:00
Rename fileNameHandler to ClassFileNameHandler and move it to the utils project
git-svn-id: https://smali.googlecode.com/svn/trunk@787 55b6fa8a-2a1e-11de-a435-ffa8d773f76a
This commit is contained in:
325
util/src/main/java/org/jf/util/ClassFileNameHandler.java
Normal file
325
util/src/main/java/org/jf/util/ClassFileNameHandler.java
Normal file
@ -0,0 +1,325 @@
|
||||
/*
|
||||
* [The "BSD licence"]
|
||||
* Copyright (c) 2010 Ben Gruver
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.jf.util;
|
||||
|
||||
import ds.tree.RadixTree;
|
||||
import ds.tree.RadixTreeImpl;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.CharBuffer;
|
||||
|
||||
/**
|
||||
* This class checks for case-insensitive file systems, and generates file names based on a given class name, that are
|
||||
* guaranteed to be unique. When "colliding" class names are found, it appends a numeric identifier to the end of the
|
||||
* class name to distinguish it from another class with a name that differes only by case. i.e. a.smali and a_2.smali
|
||||
*/
|
||||
public class ClassFileNameHandler {
|
||||
private PackageNameEntry top;
|
||||
private String fileExtension;
|
||||
|
||||
public ClassFileNameHandler(File path, String fileExtension) {
|
||||
this.top = new PackageNameEntry(path);
|
||||
this.fileExtension = fileExtension;
|
||||
}
|
||||
|
||||
public File getUniqueFilenameForClass(String className) {
|
||||
//class names should be passed in the normal dalvik style, with a leading L, a trailing ;, and using
|
||||
//'/' as a separator.
|
||||
if (className.charAt(0) != 'L' || className.charAt(className.length()-1) != ';') {
|
||||
throw new RuntimeException("Not a valid dalvik class name");
|
||||
}
|
||||
|
||||
String blah;
|
||||
int packageElementCount = 1;
|
||||
for (int i=1; i<className.length()-1; i++) {
|
||||
if (className.charAt(i) == '/') {
|
||||
packageElementCount++;
|
||||
}
|
||||
}
|
||||
|
||||
String[] packageElements = new String[packageElementCount];
|
||||
int elementIndex = 0;
|
||||
int elementStart = 1;
|
||||
for (int i=1; i<className.length()-1; i++) {
|
||||
if (className.charAt(i) == '/') {
|
||||
//if the first char after the initial L is a '/', or if there are
|
||||
//two consecutive '/'
|
||||
if (i-elementStart==0) {
|
||||
throw new RuntimeException("Not a valid dalvik class name");
|
||||
}
|
||||
|
||||
packageElements[elementIndex++] = className.substring(elementStart, i);
|
||||
elementStart = ++i;
|
||||
}
|
||||
}
|
||||
|
||||
//at this point, we have added all the package elements to packageElements, but still need to add
|
||||
//the final class name. elementStart should point to the beginning of the class name
|
||||
|
||||
//this will be true if the class ends in a '/', i.e. Lsome/package/className/;
|
||||
if (elementStart >= className.length()-1) {
|
||||
throw new RuntimeException("Not a valid dalvik class name");
|
||||
}
|
||||
packageElements[elementIndex] = className.substring(elementStart, className.length()-1);
|
||||
|
||||
return top.addUniqueChild(packageElements, 0);
|
||||
}
|
||||
|
||||
private abstract class FileSystemEntry {
|
||||
public final File file;
|
||||
|
||||
public FileSystemEntry(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
public abstract File addUniqueChild(String[] pathElements, int pathElementsIndex);
|
||||
|
||||
public FileSystemEntry makeVirtual(File parent) {
|
||||
return new VirtualGroupEntry(this, parent);
|
||||
}
|
||||
}
|
||||
|
||||
private class PackageNameEntry extends FileSystemEntry {
|
||||
//this contains the FileSystemEntries for all of this package's children
|
||||
//the associated keys are all lowercase
|
||||
private RadixTree<FileSystemEntry> children = new RadixTreeImpl<FileSystemEntry>();
|
||||
|
||||
public PackageNameEntry(File parent, String name) {
|
||||
super(new File(parent, name));
|
||||
}
|
||||
|
||||
public PackageNameEntry(File path) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File addUniqueChild(String[] pathElements, int pathElementsIndex) {
|
||||
String elementName;
|
||||
String elementNameLower;
|
||||
|
||||
if (pathElementsIndex == pathElements.length - 1) {
|
||||
elementName = pathElements[pathElementsIndex] + fileExtension;
|
||||
} else {
|
||||
elementName = pathElements[pathElementsIndex];
|
||||
}
|
||||
elementNameLower = elementName.toLowerCase();
|
||||
|
||||
FileSystemEntry existingEntry = children.find(elementNameLower);
|
||||
if (existingEntry != null) {
|
||||
FileSystemEntry virtualEntry = existingEntry;
|
||||
//if there is already another entry with the same name but different case, we need to
|
||||
//add a virtual group, and then add the existing entry and the new entry to that group
|
||||
if (!(existingEntry instanceof VirtualGroupEntry)) {
|
||||
if (existingEntry.file.getName().equals(elementName)) {
|
||||
if (pathElementsIndex == pathElements.length - 1) {
|
||||
return existingEntry.file;
|
||||
} else {
|
||||
return existingEntry.addUniqueChild(pathElements, pathElementsIndex + 1);
|
||||
}
|
||||
} else {
|
||||
virtualEntry = existingEntry.makeVirtual(file);
|
||||
children.replace(elementNameLower, virtualEntry);
|
||||
}
|
||||
}
|
||||
|
||||
return virtualEntry.addUniqueChild(pathElements, pathElementsIndex);
|
||||
}
|
||||
|
||||
if (pathElementsIndex == pathElements.length - 1) {
|
||||
ClassNameEntry classNameEntry = new ClassNameEntry(file, elementName);
|
||||
children.insert(elementNameLower, classNameEntry);
|
||||
return classNameEntry.file;
|
||||
} else {
|
||||
PackageNameEntry packageNameEntry = new PackageNameEntry(file, elementName);
|
||||
children.insert(elementNameLower, packageNameEntry);
|
||||
return packageNameEntry.addUniqueChild(pathElements, pathElementsIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A virtual group that groups together file system entries with the same name, differing only in case
|
||||
*/
|
||||
private class VirtualGroupEntry extends FileSystemEntry {
|
||||
//this contains the FileSystemEntries for all of the files/directories in this group
|
||||
//the key is the unmodified name of the entry, before it is modified to be made unique (if needed).
|
||||
private RadixTree<FileSystemEntry> groupEntries = new RadixTreeImpl<FileSystemEntry>();
|
||||
|
||||
//whether the containing directory is case sensitive or not.
|
||||
//-1 = unset
|
||||
//0 = false;
|
||||
//1 = true;
|
||||
private int isCaseSensitive = -1;
|
||||
|
||||
public VirtualGroupEntry(FileSystemEntry firstChild, File parent) {
|
||||
super(parent);
|
||||
|
||||
//use the name of the first child in the group as-is
|
||||
groupEntries.insert(firstChild.file.getName(), firstChild);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File addUniqueChild(String[] pathElements, int pathElementsIndex) {
|
||||
String elementName = pathElements[pathElementsIndex];
|
||||
|
||||
if (pathElementsIndex == pathElements.length - 1) {
|
||||
elementName = elementName + fileExtension;
|
||||
}
|
||||
|
||||
FileSystemEntry existingEntry = groupEntries.find(elementName);
|
||||
if (existingEntry != null) {
|
||||
if (pathElementsIndex == pathElements.length - 1) {
|
||||
return existingEntry.file;
|
||||
} else {
|
||||
return existingEntry.addUniqueChild(pathElements, pathElementsIndex+1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (pathElementsIndex == pathElements.length - 1) {
|
||||
String fileName;
|
||||
if (!isCaseSensitive()) {
|
||||
fileName = pathElements[pathElementsIndex] + "." + (groupEntries.getSize()+1) + fileExtension;
|
||||
} else {
|
||||
fileName = elementName;
|
||||
}
|
||||
|
||||
ClassNameEntry classNameEntry = new ClassNameEntry(file, fileName);
|
||||
groupEntries.insert(elementName, classNameEntry);
|
||||
return classNameEntry.file;
|
||||
} else {
|
||||
String fileName;
|
||||
if (!isCaseSensitive()) {
|
||||
fileName = pathElements[pathElementsIndex] + "." + (groupEntries.getSize()+1);
|
||||
} else {
|
||||
fileName = elementName;
|
||||
}
|
||||
|
||||
PackageNameEntry packageNameEntry = new PackageNameEntry(file, fileName);
|
||||
groupEntries.insert(elementName, packageNameEntry);
|
||||
return packageNameEntry.addUniqueChild(pathElements, pathElementsIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCaseSensitive() {
|
||||
if (isCaseSensitive != -1) {
|
||||
return isCaseSensitive == 1;
|
||||
}
|
||||
|
||||
File path = file;
|
||||
|
||||
if (path.exists() && path.isFile()) {
|
||||
path = path.getParentFile();
|
||||
}
|
||||
|
||||
if ((!file.exists() && !file.mkdirs())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
boolean result = testCaseSensitivity(path);
|
||||
isCaseSensitive = result?1:0;
|
||||
return result;
|
||||
} catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean testCaseSensitivity(File path) throws IOException {
|
||||
int num = 1;
|
||||
File f, f2;
|
||||
do {
|
||||
f = new File(path, "test." + num);
|
||||
f2 = new File(path, "TEST." + num++);
|
||||
} while(f.exists() || f2.exists());
|
||||
|
||||
try {
|
||||
try {
|
||||
FileWriter writer = new FileWriter(f);
|
||||
writer.write("test");
|
||||
writer.flush();
|
||||
writer.close();
|
||||
} catch (IOException ex) {
|
||||
try {f.delete();} catch (Exception ex2) {}
|
||||
throw ex;
|
||||
}
|
||||
|
||||
if (f2.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (f2.createNewFile()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
//the above 2 tests should catch almost all cases. But maybe there was a failure while creating f2
|
||||
//that isn't related to case sensitivity. Let's see if we can open the file we just created using
|
||||
//f2
|
||||
try {
|
||||
CharBuffer buf = CharBuffer.allocate(32);
|
||||
FileReader reader = new FileReader(f2);
|
||||
|
||||
while (reader.read(buf) != -1 && buf.length() < 4);
|
||||
if (buf.length() == 4 && buf.toString().equals("test")) {
|
||||
return false;
|
||||
} else {
|
||||
//we probably shouldn't get here. If the filesystem was case-sensetive, creating a new
|
||||
//FileReader should have thrown a FileNotFoundException. Otherwise, we should have opened
|
||||
//the file and read in the string "test". It's remotely possible that someone else modified
|
||||
//the file after we created it. Let's be safe and return false here as well
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
} catch (FileNotFoundException ex) {
|
||||
return true;
|
||||
}
|
||||
} finally {
|
||||
try { f.delete(); } catch (Exception ex) {}
|
||||
try { f2.delete(); } catch (Exception ex) {}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileSystemEntry makeVirtual(File parent) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
private class ClassNameEntry extends FileSystemEntry {
|
||||
public ClassNameEntry(File parent, String name) {
|
||||
super(new File(parent, name));
|
||||
}
|
||||
|
||||
@Override
|
||||
public File addUniqueChild(String[] pathElements, int pathElementsIndex) {
|
||||
assert false;
|
||||
return file;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user