1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21package proguard.obfuscate;
22
23import java.io.*;
24
25
26/**
27 * This class can parse mapping files and invoke a processor for each of the
28 * mapping entries.
29 *
30 * @author Eric Lafortune
31 */
32public class MappingReader
33{
34    private final File mappingFile;
35
36
37    public MappingReader(File mappingFile)
38    {
39        this.mappingFile = mappingFile;
40    }
41
42
43    /**
44     * Reads the mapping file, presenting all of the encountered mapping entries
45     * to the given processor.
46     */
47    public void pump(MappingProcessor mappingProcessor) throws IOException
48    {
49        LineNumberReader reader = new LineNumberReader(
50                                  new BufferedReader(
51                                  new FileReader(mappingFile)));
52        try
53        {
54            String className = null;
55
56            // Read the subsequent class mappings and class member mappings.
57            while (true)
58            {
59                String line = reader.readLine();
60
61                if (line == null)
62                {
63                    break;
64                }
65
66                line = line.trim();
67
68                // The distinction between a class mapping and a class
69                // member mapping is the initial whitespace.
70                if (line.endsWith(":"))
71                {
72                    // Process the class mapping and remember the class's
73                    // old name.
74                    className = processClassMapping(line, mappingProcessor);
75                }
76                else if (className != null)
77                {
78                    // Process the class member mapping, in the context of the
79                    // current old class name.
80                    processClassMemberMapping(className, line, mappingProcessor);
81                }
82            }
83        }
84        catch (IOException ex)
85        {
86            throw new IOException("Can't process mapping file (" + ex.getMessage() + ")");
87        }
88        finally
89        {
90            try
91            {
92                reader.close();
93            }
94            catch (IOException ex)
95            {
96                // This shouldn't happen.
97            }
98        }
99    }
100
101
102    /**
103     * Parses the given line with a class mapping and processes the
104     * results with the given mapping processor. Returns the old class name,
105     * or null if any subsequent class member lines can be ignored.
106     */
107    private String processClassMapping(String           line,
108                                       MappingProcessor mappingProcessor)
109    {
110        // See if we can parse "___ -> ___:", containing the original
111        // class name and the new class name.
112
113        int arrowIndex = line.indexOf("->");
114        if (arrowIndex < 0)
115        {
116            return null;
117        }
118
119        int colonIndex = line.indexOf(':', arrowIndex + 2);
120        if (colonIndex < 0)
121        {
122            return null;
123        }
124
125        // Extract the elements.
126        String className    = line.substring(0, arrowIndex).trim();
127        String newClassName = line.substring(arrowIndex + 2, colonIndex).trim();
128
129        // Process this class name mapping.
130        boolean interested = mappingProcessor.processClassMapping(className, newClassName);
131
132        return interested ? className : null;
133    }
134
135
136    /**
137     * Parses the given line with a class member mapping and processes the
138     * results with the given mapping processor.
139     */
140    private void processClassMemberMapping(String           className,
141                                           String           line,
142                                           MappingProcessor mappingProcessor)
143    {
144        // See if we can parse "___:___:___ ___(___) -> ___",
145        // containing the optional line numbers, the return type, the original
146        // field/method name, optional arguments, and the new field/method name.
147
148        int colonIndex1    =                           line.indexOf(':');
149        int colonIndex2    = colonIndex1    < 0 ? -1 : line.indexOf(':', colonIndex1    + 1);
150        int spaceIndex     =                           line.indexOf(' ', colonIndex2    + 2);
151        int argumentIndex1 =                           line.indexOf('(', spaceIndex     + 1);
152        int argumentIndex2 = argumentIndex1 < 0 ? -1 : line.indexOf(')', argumentIndex1 + 1);
153        int arrowIndex     =                           line.indexOf("->", Math.max(spaceIndex, argumentIndex2) + 1);
154
155        if (spaceIndex < 0 ||
156            arrowIndex < 0)
157        {
158            return;
159        }
160
161        // Extract the elements.
162        String type    = line.substring(colonIndex2 + 1, spaceIndex).trim();
163        String name    = line.substring(spaceIndex + 1, argumentIndex1 >= 0 ? argumentIndex1 : arrowIndex).trim();
164        String newName = line.substring(arrowIndex + 2).trim();
165
166        // Process this class member mapping.
167        if (type.length()    > 0 &&
168            name.length()    > 0 &&
169            newName.length() > 0)
170        {
171            // Is it a field or a method?
172            if (argumentIndex2 < 0)
173            {
174                mappingProcessor.processFieldMapping(className, type, name, newName);
175            }
176            else
177            {
178                int firstLineNumber = 0;
179                int lastLineNumber  = 0;
180
181                if (colonIndex2 > 0)
182                {
183                    firstLineNumber = Integer.parseInt(line.substring(0, colonIndex1).trim());
184                    lastLineNumber  = Integer.parseInt(line.substring(colonIndex1 + 1, colonIndex2).trim());
185                }
186
187                String arguments = line.substring(argumentIndex1 + 1, argumentIndex2).trim();
188
189                mappingProcessor.processMethodMapping(className,
190                                                      firstLineNumber,
191                                                      lastLineNumber,
192                                                      type,
193                                                      name,
194                                                      arguments,
195                                                      newName);
196            }
197        }
198    }
199}
200