<commit_before>package com.tencent.mm.androlib.res.decoder;

import com.mindprod.ledatastream.LEDataInputStream;
import com.mindprod.ledatastream.LEDataOutputStream;
import com.tencent.mm.androlib.AndrolibException;
import com.tencent.mm.androlib.ApkDecoder;
import com.tencent.mm.androlib.res.data.ResPackage;
import com.tencent.mm.androlib.res.data.ResType;
import com.tencent.mm.resourceproguard.Configuration;
import com.tencent.mm.util.ExtDataInput;
import com.tencent.mm.util.ExtDataOutput;
import com.tencent.mm.util.FileOperation;
import com.tencent.mm.util.TypedValue;
import com.tencent.mm.util.Utils;

import java.io.BufferedWriter;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Pattern;


/**
 * @author shwenzhang
 */
public class ARSCDecoder {


    private final static short  ENTRY_FLAG_COMPLEX = 0x0001;
    private static final Logger LOGGER             = Logger.getLogger(ARSCDecoder.class.getName());
    private static final int    KNOWN_CONFIG_BYTES = 38;

    public static Map<Integer, String> mTableStringsProguard = new LinkedHashMap<>();

    private ExtDataInput  mIn;
    private ExtDataOutput mOut;
    private Header        mHeader;
    private StringBlock   mTableStrings;
    private StringBlock   mTypeNames;
    private StringBlock   mSpecNames;
    private ResPackage    mPkg;
    private ResType       mType;
    private ResPackage[]  mPkgs;
    private int[]         mPkgsLenghtChange;
    private int mTableLenghtChange = 0;
    private int mResId;
    private int mCurTypeID    = -1;
    private int mCurEntryID   = -1;
    private int mCurPackageID = -1;

    private ProguardStringBuilder mProguardBuilder;
    private boolean mShouldProguardForType = false;
    private       Writer               mMappingWriter;
    private final Map<String, String>  mOldFileName;
    private final Map<String, Integer> mCurSpecNameToPos;
    private final HashSet<String>      mShouldProguardTypeSet;
    private final ApkDecoder           mApkDecoder;


    private ARSCDecoder(InputStream arscStream, ApkDecoder decoder) throws AndrolibException, IOException {
        mOldFileName = new LinkedHashMap<>();
        mCurSpecNameToPos = new LinkedHashMap<>();
        mShouldProguardTypeSet = new HashSet<>();
        mIn = new ExtDataInput(new LEDataInputStream(arscStream));
        mApkDecoder = decoder;
        proguardFileName();
    }

    private ARSCDecoder(InputStream arscStream, ApkDecoder decoder, ResPackage[] pkgs) throws FileNotFoundException {
        mOldFileName = new LinkedHashMap<>();
        mCurSpecNameToPos = new LinkedHashMap<>();
        mShouldProguardTypeSet = new HashSet<>();
        mApkDecoder = decoder;
        mIn = new ExtDataInput(new LEDataInputStream(arscStream));
        mOut = new ExtDataOutput(new LEDataOutputStream(new FileOutputStream(mApkDecoder.getOutTempARSCFile(), false)));
        mPkgs = pkgs;
        mPkgsLenghtChange = new int[pkgs.length];
    }

    public static ResPackage[] decode(InputStream arscStream, ApkDecoder apkDecoder
    )
        throws AndrolibException {
        try {
            ARSCDecoder decoder = new ARSCDecoder(arscStream, apkDecoder);
            ResPackage[] pkgs = decoder.readTable();

            return pkgs;
        } catch (IOException ex) {
            throw new AndrolibException("Could not decode arsc file", ex);
        }
    }

    public static void write(InputStream arscStream,
                             ApkDecoder decoder, ResPackage[] pkgs)
        throws AndrolibException {
        try {
            ARSCDecoder writer = new ARSCDecoder(arscStream, decoder, pkgs);
            writer.writeTable();

        } catch (IOException ex) {
            throw new AndrolibException("Could not decode arsc file", ex);
        }
    }

    private void proguardFileName() throws IOException, AndrolibException {
        mMappingWriter = new BufferedWriter(new FileWriter(mApkDecoder.getResMappingFile(), false));

        mProguardBuilder = new ProguardStringBuilder();
        mProguardBuilder.reset();

        final Configuration config = mApkDecoder.getConfig();

        File rawResFile = mApkDecoder.getRawResFile();

        File[] resFiles = rawResFile.listFiles();

        //需要看看哪些类型是要混淆文件路径的
        for (File resFile : resFiles) {
            String raw = resFile.getName();
            if (raw.contains("-")) {
                raw = raw.substring(0, raw.indexOf("-"));
            }
            mShouldProguardTypeSet.add(raw);
        }

        if (!config.mKeepRoot) {
            //需要保持之前的命名方式
            if (config.mUseKeepMapping) {
                HashMap<String, String> fileMapping = config.mOldFileMapping;
                List<String> keepFileNames = new ArrayList<String>();
                //这里面为了兼容以前，也需要用以前的文件名前缀，即res混淆成什么
                String resRoot = null;
                for (String name : fileMapping.values()) {
                    int dot = name.indexOf("/");
                    if (dot == -1) {
                        throw new IOException(
                            String.format("the old mapping res file path should be like r/a, yours %s\n", name)
                        );
                    }
                    resRoot = name.substring(0, dot);
                    keepFileNames.add(name.substring(dot + 1));
                }
                //去掉所有之前保留的命名，为了简单操作，mapping里面有的都去掉
                mProguardBuilder.removeStrings(keepFileNames);

                for (File resFile : resFiles) {
                    String raw = "res" + "/" + resFile.getName();
                    if (fileMapping.containsKey(raw)) {
                        mOldFileName.put(raw, fileMapping.get(raw));
                    } else {
                        System.out.printf("can not find the file mapping %s\n", raw);
                        mOldFileName.put(raw, resRoot + "/" + mProguardBuilder.getReplaceString());
                    }
                }
            } else {
                for (int i = 0; i < resFiles.length; i++) {
                    //这里也要用linux的分隔符,如果普通的话，就是r
                    mOldFileName.put("res" + "/" + resFiles[i].getName(), TypedValue.RES_FILE_PATH + "/" + mProguardBuilder.getReplaceString());
                }
            }
            generalFileResMapping();
        }
<commit_msg>fix issue #72: java.io.IOException: Crash when open keepmapping with a empty mapping file<commit_after>package com.tencent.mm.androlib.res.decoder;

import com.mindprod.ledatastream.LEDataInputStream;
import com.mindprod.ledatastream.LEDataOutputStream;
import com.tencent.mm.androlib.AndrolibException;
import com.tencent.mm.androlib.ApkDecoder;
import com.tencent.mm.androlib.res.data.ResPackage;
import com.tencent.mm.androlib.res.data.ResType;
import com.tencent.mm.resourceproguard.Configuration;
import com.tencent.mm.util.ExtDataInput;
import com.tencent.mm.util.ExtDataOutput;
import com.tencent.mm.util.FileOperation;
import com.tencent.mm.util.TypedValue;
import com.tencent.mm.util.Utils;

import java.io.BufferedWriter;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Pattern;


/**
 * @author shwenzhang
 */
public class ARSCDecoder {


    private final static short  ENTRY_FLAG_COMPLEX = 0x0001;
    private static final Logger LOGGER             = Logger.getLogger(ARSCDecoder.class.getName());
    private static final int    KNOWN_CONFIG_BYTES = 38;

    public static Map<Integer, String> mTableStringsProguard = new LinkedHashMap<>();

    private ExtDataInput  mIn;
    private ExtDataOutput mOut;
    private Header        mHeader;
    private StringBlock   mTableStrings;
    private StringBlock   mTypeNames;
    private StringBlock   mSpecNames;
    private ResPackage    mPkg;
    private ResType       mType;
    private ResPackage[]  mPkgs;
    private int[]         mPkgsLenghtChange;
    private int mTableLenghtChange = 0;
    private int mResId;
    private int mCurTypeID    = -1;
    private int mCurEntryID   = -1;
    private int mCurPackageID = -1;

    private ProguardStringBuilder mProguardBuilder;
    private boolean mShouldProguardForType = false;
    private       Writer               mMappingWriter;
    private final Map<String, String>  mOldFileName;
    private final Map<String, Integer> mCurSpecNameToPos;
    private final HashSet<String>      mShouldProguardTypeSet;
    private final ApkDecoder           mApkDecoder;


    private ARSCDecoder(InputStream arscStream, ApkDecoder decoder) throws AndrolibException, IOException {
        mOldFileName = new LinkedHashMap<>();
        mCurSpecNameToPos = new LinkedHashMap<>();
        mShouldProguardTypeSet = new HashSet<>();
        mIn = new ExtDataInput(new LEDataInputStream(arscStream));
        mApkDecoder = decoder;
        proguardFileName();
    }

    private ARSCDecoder(InputStream arscStream, ApkDecoder decoder, ResPackage[] pkgs) throws FileNotFoundException {
        mOldFileName = new LinkedHashMap<>();
        mCurSpecNameToPos = new LinkedHashMap<>();
        mShouldProguardTypeSet = new HashSet<>();
        mApkDecoder = decoder;
        mIn = new ExtDataInput(new LEDataInputStream(arscStream));
        mOut = new ExtDataOutput(new LEDataOutputStream(new FileOutputStream(mApkDecoder.getOutTempARSCFile(), false)));
        mPkgs = pkgs;
        mPkgsLenghtChange = new int[pkgs.length];
    }

    public static ResPackage[] decode(InputStream arscStream, ApkDecoder apkDecoder
    )
        throws AndrolibException {
        try {
            ARSCDecoder decoder = new ARSCDecoder(arscStream, apkDecoder);
            ResPackage[] pkgs = decoder.readTable();

            return pkgs;
        } catch (IOException ex) {
            throw new AndrolibException("Could not decode arsc file", ex);
        }
    }

    public static void write(InputStream arscStream,
                             ApkDecoder decoder, ResPackage[] pkgs)
        throws AndrolibException {
        try {
            ARSCDecoder writer = new ARSCDecoder(arscStream, decoder, pkgs);
            writer.writeTable();

        } catch (IOException ex) {
            throw new AndrolibException("Could not decode arsc file", ex);
        }
    }

    private void proguardFileName() throws IOException, AndrolibException {
        mMappingWriter = new BufferedWriter(new FileWriter(mApkDecoder.getResMappingFile(), false));

        mProguardBuilder = new ProguardStringBuilder();
        mProguardBuilder.reset();

        final Configuration config = mApkDecoder.getConfig();

        File rawResFile = mApkDecoder.getRawResFile();

        File[] resFiles = rawResFile.listFiles();

        //需要看看哪些类型是要混淆文件路径的
        for (File resFile : resFiles) {
            String raw = resFile.getName();
            if (raw.contains("-")) {
                raw = raw.substring(0, raw.indexOf("-"));
            }
            mShouldProguardTypeSet.add(raw);
        }

        if (!config.mKeepRoot) {
            //需要保持之前的命名方式
            if (config.mUseKeepMapping) {
                HashMap<String, String> fileMapping = config.mOldFileMapping;
                List<String> keepFileNames = new ArrayList<String>();
                //这里面为了兼容以前，也需要用以前的文件名前缀，即res混淆成什么
                String resRoot = TypedValue.RES_FILE_PATH;
                for (String name : fileMapping.values()) {
                    int dot = name.indexOf("/");
                    if (dot == -1) {
                        throw new IOException(
                            String.format("the old mapping res file path should be like r/a, yours %s\n", name)
                        );
                    }
                    resRoot = name.substring(0, dot);
                    keepFileNames.add(name.substring(dot + 1));
                }
                //去掉所有之前保留的命名，为了简单操作，mapping里面有的都去掉
                mProguardBuilder.removeStrings(keepFileNames);

                for (File resFile : resFiles) {
                    String raw = "res" + "/" + resFile.getName();
                    if (fileMapping.containsKey(raw)) {
                        mOldFileName.put(raw, fileMapping.get(raw));
                    } else {
                        System.out.printf("can not find the file mapping %s\n", raw);
                        mOldFileName.put(raw, resRoot + "/" + mProguardBuilder.getReplaceString());
                    }
                }
            } else {
                for (int i = 0; i < resFiles.length; i++) {
                    //这里也要用linux的分隔符,如果普通的话，就是r
                    mOldFileName.put("res" + "/" + resFiles[i].getName(), TypedValue.RES_FILE_PATH + "/" + mProguardBuilder.getReplaceString());
                }
            }
            generalFileResMapping();
        }

{'fraction_non_alphanumeric': 0.09252513478070815, 'fraction_numerical': 0.002477050852396911, 'mean_word_length': 2.3960910440376053, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7154327', 'n_tokens_mistral': 4029, 'n_tokens_neox': 3925, 'n_words': 860}