1/**
2 * Copyright 2007 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.tonicsystems.jarjar;
18
19import com.tonicsystems.jarjar.util.*;
20import java.io.*;
21import java.util.*;
22import org.objectweb.asm.*;
23import org.objectweb.asm.Type;
24import org.objectweb.asm.commons.*;
25
26// TODO: this can probably be refactored into JarClassVisitor, etc.
27class KeepProcessor extends Remapper implements JarProcessor
28{
29    private final ClassVisitor cv = new RemappingClassAdapter(new EmptyClassVisitor(), this);
30    private final List<Wildcard> wildcards;
31    private final List<String> roots = new ArrayList<String>();
32    private final Map<String, Set<String>> depend = new HashMap<String, Set<String>>();
33
34    public KeepProcessor(List<Keep> patterns) {
35        wildcards = PatternElement.createWildcards(patterns);
36    }
37
38    public boolean isEnabled() {
39        return !wildcards.isEmpty();
40    }
41
42    public Set<String> getExcludes() {
43        Set<String> closure = new HashSet<String>();
44        closureHelper(closure, roots);
45        Set<String> removable = new HashSet<String>(depend.keySet());
46        removable.removeAll(closure);
47        return removable;
48    }
49
50    private void closureHelper(Set<String> closure, Collection<String> process) {
51        if (process == null)
52            return;
53        for (String name : process) {
54            if (closure.add(name))
55                closureHelper(closure, depend.get(name));
56        }
57    }
58
59    private Set<String> curSet;
60    private byte[] buf = new byte[0x2000];
61
62    public boolean process(EntryStruct struct) throws IOException {
63        try {
64            if (struct.name.endsWith(".class")) {
65                String name = struct.name.substring(0, struct.name.length() - 6);
66                for (Wildcard wildcard : wildcards)
67                    if (wildcard.matches(name))
68                        roots.add(name);
69                depend.put(name, curSet = new HashSet<String>());
70                new ClassReader(new ByteArrayInputStream(struct.data)).accept(cv,
71                    ClassReader.EXPAND_FRAMES);
72                curSet.remove(name);
73            }
74        } catch (Exception e) {
75          System.err.println("Error reading " + struct.name + ": " + e.getMessage());
76        }
77        return true;
78    }
79
80    public String map(String key) {
81        if (key.startsWith("java/") || key.startsWith("javax/"))
82            return null;
83        curSet.add(key);
84        return null;
85    }
86
87    public Object mapValue(Object value) {
88        if (value instanceof String) {
89            String s = (String)value;
90            if (PackageRemapper.isArrayForName(s)) {
91                mapDesc(s.replace('.', '/'));
92            } else if (isForName(s)) {
93                map(s.replace('.', '/'));
94            }
95            return value;
96        } else {
97            return super.mapValue(value);
98        }
99    }
100
101    // TODO: use this for package remapping too?
102    private static boolean isForName(String value) {
103        if (value.equals(""))
104            return false;
105        for (int i = 0, len = value.length(); i < len; i++) {
106            char c = value.charAt(i);
107            if (c != '.' && !Character.isJavaIdentifierPart(c))
108                return false;
109        }
110        return true;
111    }
112}
113