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