ProGuardTask.java revision 2270795fbe0b277bfd49f40950ecaa78583175cc
1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2014 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.gradle;
22
23import groovy.lang.Closure;
24import org.gradle.api.DefaultTask;
25import org.gradle.api.file.*;
26import org.gradle.api.logging.*;
27import org.gradle.api.tasks.*;
28import proguard.*;
29import proguard.classfile.*;
30import proguard.classfile.util.ClassUtil;
31import proguard.util.ListUtil;
32
33import java.io.*;
34import java.util.*;
35
36/**
37 * This Task allows to configure and run ProGuard from Gradle.
38 *
39 * @author Eric Lafortune
40 */
41public class ProGuardTask extends DefaultTask
42{
43    // Accumulated input and output, for the sake of Gradle's lazy file
44    // resolution and lazy task execution.
45    private final List          inJarFiles         = new ArrayList();
46    private final List          inJarFilters       = new ArrayList();
47    private final List          outJarFiles        = new ArrayList();
48    private final List          outJarFilters      = new ArrayList();
49    private final List          inJarCounts        = new ArrayList();
50    private final List          libraryJarFiles    = new ArrayList();
51    private final List          libraryJarFilters  = new ArrayList();
52    private final List          configurationFiles = new ArrayList();
53
54    // Accumulated configuration.
55    private final Configuration configuration      = new Configuration();
56
57    // Field acting as a parameter for the class member specification methods.
58    private ClassSpecification classSpecification;
59
60
61    // Gradle task inputs and outputs, because annotations on the List fields
62    // (private or not) don't seem to work. Private methods don't work either,
63    // but package visible or protected methods are ok.
64
65    @InputFiles
66    protected FileCollection getInJarFileCollection()
67    {
68        return getProject().files(inJarFiles);
69    }
70
71    @Optional @OutputFiles
72    protected FileCollection getOutJarFileCollection()
73    {
74        return getProject().files(outJarFiles);
75    }
76
77    @InputFiles
78    protected FileCollection getLibraryJarFileCollection()
79    {
80        return getProject().files(libraryJarFiles);
81    }
82
83    @InputFiles
84    protected FileCollection getConfigurationFileCollection()
85    {
86        return getProject().files(configurationFiles);
87    }
88
89
90    // Convenience methods to retrieve settings from outside the task.
91
92    /**
93     * Returns the collected list of input files (directory, jar, aar, etc,
94     * represented as Object, String, File, etc).
95     */
96    public List getInJarFiles()
97    {
98        return inJarFiles;
99    }
100
101    /**
102     * Returns the collected list of filters (represented as argument Maps)
103     * corresponding to the list of input files.
104     */
105    public List getInJarFilters()
106    {
107        return inJarFilters;
108    }
109
110    /**
111     * Returns the collected list of output files (directory, jar, aar, etc,
112     * represented as Object, String, File, etc).
113     */
114    public List getOutJarFiles()
115    {
116        return outJarFiles;
117    }
118
119    /**
120     * Returns the collected list of filters (represented as argument Maps)
121     * corresponding to the list of output files.
122     */
123    public List getOutJarFilters()
124    {
125        return outJarFilters;
126    }
127
128    /**
129     * Returns the list with the numbers of input files that correspond to the
130     * list of output files.
131     *
132     * For instance, [2, 3] means that
133     *   the contents of the first 2 input files go to the first output file and
134     *   the contents of the next 3 input files go to the second output file.
135     */
136    public List getInJarCounts()
137    {
138        return inJarCounts;
139    }
140
141    /**
142     * Returns the collected list of library files (directory, jar, aar, etc,
143     * represented as Object, String, File, etc).
144     */
145    public List getLibraryJarFiles()
146    {
147        return libraryJarFiles;
148    }
149
150    /**
151     * Returns the collected list of filters (represented as argument Maps)
152     * corresponding to the list of library files.
153     */
154    public List getLibraryJarFilters()
155    {
156        return libraryJarFilters;
157    }
158
159    /**
160     * Returns the collected list of configuration files to be included
161     * (represented as Object, String, File, etc).
162     */
163    public List getConfigurationFiles()
164    {
165        return configurationFiles;
166    }
167
168
169    // Gradle task settings corresponding to all ProGuard options.
170
171    public void configuration(Object configurationFiles)
172    throws ParseException, IOException
173    {
174        // Just collect the arguments, so they can be resolved lazily.
175        this.configurationFiles.add(configurationFiles);
176    }
177
178    public void injars(Object inJarFiles)
179    throws ParseException
180    {
181        injars(null, inJarFiles);
182    }
183
184    public void injars(Map filterArgs, Object inJarFiles)
185    throws ParseException
186    {
187        // Just collect the arguments, so they can be resolved lazily.
188        this.inJarFiles.add(inJarFiles);
189        this.inJarFilters.add(filterArgs);
190    }
191
192    public void outjars(Object outJarFiles)
193    throws ParseException
194    {
195        outjars(null, outJarFiles);
196    }
197
198    public void outjars(Map filterArgs, Object outJarFiles)
199    throws ParseException
200    {
201        // Just collect the arguments, so they can be resolved lazily.
202        this.outJarFiles.add(outJarFiles);
203        this.outJarFilters.add(filterArgs);
204        this.inJarCounts.add(Integer.valueOf(inJarFiles.size()));
205    }
206
207    public void libraryjars(Object libraryJarFiles)
208    throws ParseException
209    {
210        libraryjars(null, libraryJarFiles);
211    }
212
213    public void libraryjars(Map filterArgs, Object libraryJarFiles)
214    throws ParseException
215    {
216        // Just collect the arguments, so they can be resolved lazily.
217        this.libraryJarFiles.add(libraryJarFiles);
218        this.libraryJarFilters.add(filterArgs);
219    }
220
221    // Hack: support the keyword without parentheses in Groovy.
222    public Object getskipnonpubliclibraryclasses()
223    {
224        skipnonpubliclibraryclasses();
225        return null;
226    }
227
228    public void skipnonpubliclibraryclasses()
229    {
230        configuration.skipNonPublicLibraryClasses = true;
231    }
232
233    // Hack: support the keyword without parentheses in Groovy.
234    public Object getdontskipnonpubliclibraryclassmembers()
235    {
236        dontskipnonpubliclibraryclassmembers();
237        return null;
238    }
239
240    public void dontskipnonpubliclibraryclassmembers()
241    {
242        configuration.skipNonPublicLibraryClassMembers = false;
243    }
244
245    // Hack: support the keyword without parentheses in Groovy.
246    public Object getkeepdirectories()
247    {
248        keepdirectories();
249        return null;
250    }
251
252    public void keepdirectories()
253    {
254        keepdirectories(null);
255    }
256
257    public void keepdirectories(String filter)
258    {
259        configuration.keepDirectories =
260            extendFilter(configuration.keepDirectories, filter);
261    }
262
263    public void target(String targetClassVersion)
264    {
265        configuration.targetClassVersion =
266            ClassUtil.internalClassVersion(targetClassVersion);
267    }
268
269    // Hack: support the keyword without parentheses in Groovy.
270    public Object getforceprocessing()
271    {
272        forceprocessing();
273        return null;
274    }
275
276    public void forceprocessing()
277    {
278        configuration.lastModified = Long.MAX_VALUE;
279    }
280
281    public void keep(String classSpecificationString)
282    throws ParseException
283    {
284        keep(null, classSpecificationString);
285    }
286
287    public void keep(Map    keepArgs,
288                     String classSpecificationString)
289    throws ParseException
290    {
291        configuration.keep =
292            extendClassSpecifications(configuration.keep,
293            createKeepClassSpecification(false,
294                                         true,
295                                         false,
296                                         keepArgs,
297                                         classSpecificationString));
298    }
299
300    public void keep(Map keepClassSpecificationArgs)
301    throws ParseException
302    {
303        keep(keepClassSpecificationArgs, (Closure)null);
304    }
305
306    public void keep(Map     keepClassSpecificationArgs,
307                     Closure classMembersClosure)
308    throws ParseException
309    {
310        configuration.keep =
311            extendClassSpecifications(configuration.keep,
312            createKeepClassSpecification(false,
313                                         true,
314                                         false,
315                                         keepClassSpecificationArgs,
316                                         classMembersClosure));
317    }
318
319    public void keepclassmembers(String classSpecificationString)
320    throws ParseException
321    {
322        keepclassmembers(null, classSpecificationString);
323    }
324
325    public void keepclassmembers(Map    keepArgs,
326                                 String classSpecificationString)
327    throws ParseException
328    {
329        configuration.keep =
330            extendClassSpecifications(configuration.keep,
331            createKeepClassSpecification(false,
332                                         false,
333                                         false,
334                                         keepArgs,
335                                         classSpecificationString));
336    }
337
338    public void keepclassmembers(Map keepClassSpecificationArgs)
339    throws ParseException
340    {
341        keepclassmembers(keepClassSpecificationArgs, (Closure)null);
342    }
343
344    public void keepclassmembers(Map     keepClassSpecificationArgs,
345                                 Closure classMembersClosure)
346    throws ParseException
347    {
348        configuration.keep =
349            extendClassSpecifications(configuration.keep,
350            createKeepClassSpecification(false,
351                                         false,
352                                         false,
353                                         keepClassSpecificationArgs,
354                                         classMembersClosure));
355    }
356
357    public void keepclasseswithmembers(String classSpecificationString)
358    throws ParseException
359    {
360        keepclasseswithmembers(null, classSpecificationString);
361    }
362
363    public void keepclasseswithmembers(Map    keepArgs,
364                                       String classSpecificationString)
365    throws ParseException
366    {
367        configuration.keep =
368            extendClassSpecifications(configuration.keep,
369            createKeepClassSpecification(false,
370                                         false,
371                                         true,
372                                         keepArgs,
373                                         classSpecificationString));
374    }
375
376    public void keepclasseswithmembers(Map keepClassSpecificationArgs)
377    throws ParseException
378    {
379        keepclasseswithmembers(keepClassSpecificationArgs, (Closure)null);
380    }
381
382    public void keepclasseswithmembers(Map     keepClassSpecificationArgs,
383                                       Closure classMembersClosure)
384    throws ParseException
385    {
386        configuration.keep =
387            extendClassSpecifications(configuration.keep,
388            createKeepClassSpecification(false,
389                                         false,
390                                         true,
391                                         keepClassSpecificationArgs,
392                                         classMembersClosure));
393    }
394
395    public void keepnames(String classSpecificationString)
396    throws ParseException
397    {
398        keepnames(null, classSpecificationString);
399    }
400
401    public void keepnames(Map    keepArgs,
402                          String classSpecificationString)
403    throws ParseException
404    {
405        configuration.keep =
406            extendClassSpecifications(configuration.keep,
407            createKeepClassSpecification(true,
408                                         true,
409                                         false,
410                                         keepArgs,
411                                         classSpecificationString));
412    }
413
414    public void keepnames(Map keepClassSpecificationArgs)
415    throws ParseException
416    {
417        keepnames(keepClassSpecificationArgs, (Closure)null);
418    }
419
420    public void keepnames(Map     keepClassSpecificationArgs,
421                          Closure classMembersClosure)
422    throws ParseException
423    {
424        configuration.keep =
425            extendClassSpecifications(configuration.keep,
426            createKeepClassSpecification(true,
427                                         true,
428                                         false,
429                                         keepClassSpecificationArgs,
430                                         classMembersClosure));
431    }
432
433    public void keepclassmembernames(String classSpecificationString)
434    throws ParseException
435    {
436        keepclassmembernames(null, classSpecificationString);
437    }
438
439    public void keepclassmembernames(Map    keepArgs,
440                                     String classSpecificationString)
441    throws ParseException
442    {
443        configuration.keep =
444            extendClassSpecifications(configuration.keep,
445            createKeepClassSpecification(true,
446                                         false,
447                                         false,
448                                         keepArgs,
449                                         classSpecificationString));
450    }
451
452    public void keepclassmembernames(Map keepClassSpecificationArgs)
453    throws ParseException
454    {
455        keepclassmembernames(keepClassSpecificationArgs, (Closure)null);
456    }
457
458    public void keepclassmembernames(Map     keepClassSpecificationArgs,
459                                     Closure classMembersClosure)
460    throws ParseException
461    {
462        configuration.keep =
463            extendClassSpecifications(configuration.keep,
464            createKeepClassSpecification(true,
465                                         false,
466                                         false,
467                                         keepClassSpecificationArgs,
468                                         classMembersClosure));
469    }
470
471    public void keepclasseswithmembernames(String classSpecificationString)
472    throws ParseException
473    {
474        keepclasseswithmembernames(null, classSpecificationString);
475    }
476
477    public void keepclasseswithmembernames(Map    keepArgs,
478                                           String classSpecificationString)
479    throws ParseException
480    {
481        configuration.keep =
482            extendClassSpecifications(configuration.keep,
483            createKeepClassSpecification(true,
484                                         false,
485                                         true,
486                                         keepArgs,
487                                         classSpecificationString));
488    }
489
490    public void keepclasseswithmembernames(Map keepClassSpecificationArgs)
491    throws ParseException
492    {
493        keepclasseswithmembernames(keepClassSpecificationArgs, (Closure)null);
494    }
495
496    public void keepclasseswithmembernames(Map     keepClassSpecificationArgs,
497                                           Closure classMembersClosure)
498    throws ParseException
499    {
500        configuration.keep =
501            extendClassSpecifications(configuration.keep,
502            createKeepClassSpecification(true,
503                                         false,
504                                         true,
505                                         keepClassSpecificationArgs,
506                                         classMembersClosure));
507    }
508
509    // Hack: support the keyword without parentheses in Groovy.
510    public Object getprintseeds()
511    {
512        printseeds();
513        return null;
514    }
515
516    public void printseeds()
517    {
518        configuration.printSeeds = Configuration.STD_OUT;
519    }
520
521    public void printseeds(Object printSeeds)
522    throws ParseException
523    {
524        configuration.printSeeds = getProject().file(printSeeds);
525    }
526
527    // Hack: support the keyword without parentheses in Groovy.
528    public Object getdontshrink()
529    {
530        dontshrink();
531        return null;
532    }
533
534    public void dontshrink()
535    {
536        configuration.shrink = false;
537    }
538
539    // Hack: support the keyword without parentheses in Groovy.
540    public Object getprintusage()
541    {
542        printusage();
543        return null;
544    }
545
546    public void printusage()
547    {
548        configuration.printUsage = Configuration.STD_OUT;
549    }
550
551    public void printusage(Object printUsage)
552    throws ParseException
553    {
554        configuration.printUsage = getProject().file(printUsage);
555    }
556
557    public void whyareyoukeeping(String classSpecificationString)
558    throws ParseException
559    {
560        configuration.whyAreYouKeeping =
561            extendClassSpecifications(configuration.whyAreYouKeeping,
562                                      createClassSpecification(classSpecificationString));
563    }
564
565    public void whyareyoukeeping(Map classSpecificationArgs)
566    throws ParseException
567    {
568        whyareyoukeeping(classSpecificationArgs, null);
569    }
570
571    public void whyareyoukeeping(Map     classSpecificationArgs,
572                                 Closure classMembersClosure)
573    throws ParseException
574    {
575        configuration.whyAreYouKeeping =
576            extendClassSpecifications(configuration.whyAreYouKeeping,
577            createClassSpecification(classSpecificationArgs,
578                                     classMembersClosure));
579    }
580
581    // Hack: support the keyword without parentheses in Groovy.
582    public Object getdontoptimize()
583    {
584        dontoptimize();
585        return null;
586    }
587
588    public void dontoptimize()
589    {
590        configuration.optimize = false;
591    }
592
593    public void optimizations(String filter)
594    {
595        configuration.optimizations =
596            extendFilter(configuration.optimizations, filter);
597    }
598
599
600    public void optimizationpasses(int optimizationPasses)
601    {
602        configuration.optimizationPasses = optimizationPasses;
603    }
604
605    public void assumenosideeffects(String classSpecificationString)
606    throws ParseException
607    {
608        configuration.assumeNoSideEffects =
609            extendClassSpecifications(configuration.assumeNoSideEffects,
610            createClassSpecification(classSpecificationString));
611    }
612
613    public void assumenosideeffects(Map     classSpecificationArgs,
614                                    Closure classMembersClosure)
615    throws ParseException
616    {
617        configuration.assumeNoSideEffects =
618            extendClassSpecifications(configuration.assumeNoSideEffects,
619            createClassSpecification(classSpecificationArgs,
620                                     classMembersClosure));
621    }
622
623    // Hack: support the keyword without parentheses in Groovy.
624    public Object getallowaccessmodification()
625    {
626        allowaccessmodification();
627        return null;
628    }
629
630    public void allowaccessmodification()
631    {
632        configuration.allowAccessModification = true;
633    }
634
635    // Hack: support the keyword without parentheses in Groovy.
636    public Object getmergeinterfacesaggressively()
637    {
638        mergeinterfacesaggressively();
639        return null;
640    }
641
642    public void mergeinterfacesaggressively()
643    {
644        configuration.mergeInterfacesAggressively = true;
645    }
646
647    // Hack: support the keyword without parentheses in Groovy.
648    public Object getdontobfuscate()
649    {
650        dontobfuscate();
651        return null;
652    }
653
654    public void dontobfuscate()
655    {
656        configuration.obfuscate = false;
657    }
658
659    // Hack: support the keyword without parentheses in Groovy.
660    public Object getprintmapping()
661    {
662        printmapping();
663        return null;
664    }
665
666    public void printmapping()
667    {
668        configuration.printMapping = Configuration.STD_OUT;
669    }
670
671    public void printmapping(Object printMapping)
672    throws ParseException
673    {
674        configuration.printMapping = getProject().file(printMapping);
675    }
676
677    public void applymapping(Object applyMapping)
678    throws ParseException
679    {
680        configuration.applyMapping = getProject().file(applyMapping);
681    }
682
683    public void obfuscationdictionary(Object obfuscationDictionary)
684    throws ParseException
685    {
686        configuration.obfuscationDictionary =
687            getProject().file(obfuscationDictionary);
688    }
689
690    public void classobfuscationdictionary(Object classObfuscationDictionary)
691    throws ParseException
692    {
693        configuration.classObfuscationDictionary =
694            getProject().file(classObfuscationDictionary);
695    }
696
697    public void packageobfuscationdictionary(Object packageObfuscationDictionary)
698    throws ParseException
699    {
700        configuration.packageObfuscationDictionary =
701            getProject().file(packageObfuscationDictionary);
702    }
703
704    // Hack: support the keyword without parentheses in Groovy.
705    public Object getoverloadaggressively()
706    {
707        overloadaggressively();
708        return null;
709    }
710
711    public void overloadaggressively()
712    {
713        configuration.overloadAggressively = true;
714    }
715
716    // Hack: support the keyword without parentheses in Groovy.
717    public Object getuseuniqueclassmembernames()
718    {
719        useuniqueclassmembernames();
720        return null;
721    }
722
723    public void useuniqueclassmembernames()
724    {
725        configuration.useUniqueClassMemberNames = true;
726    }
727
728    // Hack: support the keyword without parentheses in Groovy.
729    public Object getdontusemixedcaseclassnames()
730    {
731        dontusemixedcaseclassnames();
732        return null;
733    }
734
735    public void dontusemixedcaseclassnames()
736    {
737        configuration.useMixedCaseClassNames = false;
738    }
739
740    // Hack: support the keyword without parentheses in Groovy.
741    public Object getkeeppackagenames()
742    {
743        keeppackagenames();
744        return null;
745    }
746
747    public void keeppackagenames()
748    {
749        keeppackagenames(null);
750    }
751
752    public void keeppackagenames(String filter)
753    {
754        configuration.keepPackageNames =
755            extendFilter(configuration.keepPackageNames, filter, true);
756    }
757
758    // Hack: support the keyword without parentheses in Groovy.
759    public Object getflattenpackagehierarchy()
760    {
761        flattenpackagehierarchy();
762        return null;
763    }
764
765    public void flattenpackagehierarchy()
766    {
767        flattenpackagehierarchy("");
768    }
769
770    public void flattenpackagehierarchy(String flattenPackageHierarchy)
771    {
772        configuration.flattenPackageHierarchy =
773            ClassUtil.internalClassName(flattenPackageHierarchy);
774    }
775
776    // Hack: support the keyword without parentheses in Groovy.
777    public Object getrepackageclasses()
778    {
779        repackageclasses();
780        return null;
781    }
782
783    public void repackageclasses()
784    {
785        repackageclasses("");
786    }
787
788    public void repackageclasses(String repackageClasses)
789    {
790        configuration.repackageClasses =
791            ClassUtil.internalClassName(repackageClasses);
792    }
793
794    // Hack: support the keyword without parentheses in Groovy.
795    public Object getkeepattributes()
796    {
797        keepattributes();
798        return null;
799    }
800
801    public void keepattributes()
802    {
803        keepattributes(null);
804    }
805
806    public void keepattributes(String filter)
807    {
808        configuration.keepAttributes =
809            extendFilter(configuration.keepAttributes, filter);
810    }
811
812    // Hack: support the keyword without parentheses in Groovy.
813    public Object getkeepparameternames()
814    {
815        keepparameternames();
816        return null;
817    }
818
819    public void keepparameternames()
820    {
821        configuration.keepParameterNames = true;
822    }
823
824    // Hack: support the keyword without parentheses in Groovy.
825    public Object getrenamesourcefileattribute()
826    {
827        renamesourcefileattribute();
828        return null;
829    }
830
831    public void renamesourcefileattribute()
832    {
833        renamesourcefileattribute("");
834    }
835
836    public void renamesourcefileattribute(String newSourceFileAttribute)
837    {
838        configuration.newSourceFileAttribute = newSourceFileAttribute;
839    }
840
841    // Hack: support the keyword without parentheses in Groovy.
842    public Object getadaptclassstrings()
843    {
844        adaptclassstrings();
845        return null;
846    }
847
848    public void adaptclassstrings()
849    {
850        adaptclassstrings(null);
851    }
852
853    public void adaptclassstrings(String filter)
854    {
855        configuration.adaptClassStrings =
856            extendFilter(configuration.adaptClassStrings, filter, true);
857    }
858
859    // Hack: support the keyword without parentheses in Groovy.
860    public Object getadaptresourcefilenames()
861    {
862        adaptresourcefilenames();
863        return null;
864    }
865
866    public void adaptresourcefilenames()
867    {
868        adaptresourcefilenames(null);
869    }
870
871    public void adaptresourcefilenames(String filter)
872    {
873        configuration.adaptResourceFileNames =
874            extendFilter(configuration.adaptResourceFileNames, filter);
875    }
876
877    // Hack: support the keyword without parentheses in Groovy.
878    public Object getadaptresourcefilecontents()
879    {
880        adaptresourcefilecontents();
881        return null;
882    }
883
884    public void adaptresourcefilecontents()
885    {
886        adaptresourcefilecontents(null);
887    }
888
889    public void adaptresourcefilecontents(String filter)
890    {
891        configuration.adaptResourceFileContents =
892            extendFilter(configuration.adaptResourceFileContents, filter);
893    }
894
895    // Hack: support the keyword without parentheses in Groovy.
896    public Object getdontpreverify()
897    {
898        dontpreverify();
899        return null;
900    }
901
902    public void dontpreverify()
903    {
904        configuration.preverify = false;
905    }
906
907    // Hack: support the keyword without parentheses in Groovy.
908    public Object getmicroedition()
909    {
910        microedition();
911        return null;
912    }
913
914    public void microedition()
915    {
916        configuration.microEdition = true;
917    }
918
919    // Hack: support the keyword without parentheses in Groovy.
920    public Object getverbose()
921    {
922        verbose();
923        return null;
924    }
925
926    public void verbose()
927    {
928        configuration.verbose = true;
929    }
930
931    // Hack: support the keyword without parentheses in Groovy.
932    public Object getdontnote()
933    {
934        dontnote();
935        return null;
936    }
937
938    public void dontnote()
939    {
940        dontnote(null);
941    }
942
943    public void dontnote(String filter)
944    {
945        configuration.note = extendFilter(configuration.note, filter, true);
946    }
947
948
949    // Hack: support the keyword without parentheses in Groovy.
950    public Object getdontwarn()
951    {
952        dontwarn();
953        return null;
954    }
955
956    public void dontwarn()
957    {
958        dontwarn(null);
959    }
960
961    public void dontwarn(String filter)
962    {
963        configuration.warn = extendFilter(configuration.warn, filter, true);
964    }
965
966
967    // Hack: support the keyword without parentheses in Groovy.
968    public Object getignorewarnings()
969    {
970        ignorewarnings();
971        return null;
972    }
973
974    public void ignorewarnings()
975    {
976        configuration.ignoreWarnings = true;
977    }
978
979    // Hack: support the keyword without parentheses in Groovy.
980    public Object getprintconfiguration()
981    {
982        printconfiguration();
983        return null;
984    }
985
986    public void printconfiguration()
987    {
988        configuration.printConfiguration = Configuration.STD_OUT;
989    }
990
991    public void printconfiguration(Object printConfiguration)
992    throws ParseException
993    {
994        configuration.printConfiguration =
995            getProject().file(printConfiguration);
996    }
997
998    // Hack: support the keyword without parentheses in Groovy.
999    public Object getdump()
1000    {
1001        dump();
1002        return null;
1003    }
1004
1005    public void dump()
1006    {
1007        configuration.dump = Configuration.STD_OUT;
1008    }
1009
1010    public void dump(Object dump)
1011    throws ParseException
1012    {
1013        configuration.dump = getProject().file(dump);
1014    }
1015
1016
1017    // Class member methods.
1018
1019    public void field(Map memberSpecificationArgs)
1020    throws ParseException
1021    {
1022        if (classSpecification == null)
1023        {
1024            throw new IllegalArgumentException("The 'field' method can only be used nested inside a class specification.");
1025        }
1026
1027        classSpecification.addField(createMemberSpecification(false,
1028                                                              false,
1029                                                              memberSpecificationArgs));
1030    }
1031
1032
1033    public void constructor(Map memberSpecificationArgs)
1034    throws ParseException
1035    {
1036        if (classSpecification == null)
1037        {
1038            throw new IllegalArgumentException("The 'constructor' method can only be used nested inside a class specification.");
1039        }
1040
1041        classSpecification.addMethod(createMemberSpecification(true,
1042                                                               true,
1043                                                               memberSpecificationArgs));
1044    }
1045
1046
1047    public void method(Map memberSpecificationArgs)
1048    throws ParseException
1049    {
1050        if (classSpecification == null)
1051        {
1052            throw new IllegalArgumentException("The 'method' method can only be used nested inside a class specification.");
1053        }
1054
1055        classSpecification.addMethod(createMemberSpecification(true,
1056                                                               false,
1057                                                               memberSpecificationArgs));
1058    }
1059
1060
1061    // Gradle task execution.
1062
1063    @TaskAction
1064    public void proguard()
1065    throws ParseException, IOException
1066    {
1067        // Let the logging manager capture the standard output and errors from
1068        // ProGuard.
1069        LoggingManager loggingManager = getLogging();
1070        loggingManager.captureStandardOutput(LogLevel.INFO);
1071        loggingManager.captureStandardError(LogLevel.WARN);
1072
1073        // Run ProGuard with the collected configuration.
1074        new ProGuard(getConfiguration()).execute();
1075
1076    }
1077
1078
1079    /**
1080     * Returns the configuration collected so far, resolving files and
1081     * reading included configurations.
1082     */
1083    private Configuration getConfiguration() throws IOException, ParseException
1084    {
1085        // Weave the input jars and the output jars into a single class path,
1086        // with lazy resolution of the files.
1087        configuration.programJars = new ClassPath();
1088
1089        int outJarIndex = 0;
1090
1091        int inJarCount = inJarCounts.size() == 0 ? -1 :
1092                ((Integer)inJarCounts.get(0)).intValue();
1093
1094        for (int inJarIndex = 0; inJarIndex < inJarFiles.size(); inJarIndex++)
1095        {
1096            configuration.programJars =
1097                extendClassPath(configuration.programJars,
1098                                inJarFiles.get(inJarIndex),
1099                                (Map)inJarFilters.get(inJarIndex),
1100                                false);
1101
1102            while (inJarIndex == inJarCount - 1)
1103            {
1104                configuration.programJars =
1105                    extendClassPath(configuration.programJars,
1106                                    outJarFiles.get(outJarIndex),
1107                                    (Map)outJarFilters.get(outJarIndex),
1108                                    true);
1109
1110                outJarIndex++;
1111
1112                inJarCount = inJarCounts.size() == outJarIndex ? -1 :
1113                    ((Integer)inJarCounts.get(outJarIndex)).intValue();
1114            }
1115        }
1116
1117        // Copy the library jars into a single class path, with lazy resolution
1118        // of the files.
1119        configuration.libraryJars = new ClassPath();
1120
1121        for (int libraryJarIndex = 0; libraryJarIndex < libraryJarFiles.size(); libraryJarIndex++)
1122        {
1123            configuration.libraryJars =
1124                extendClassPath(configuration.libraryJars,
1125                                libraryJarFiles.get(libraryJarIndex),
1126                                (Map)libraryJarFilters.get(libraryJarIndex),
1127                                false);
1128        }
1129
1130        // Lazily apply the external configuration files.
1131        ConfigurableFileCollection fileCollection =
1132            getProject().files(configurationFiles);
1133
1134        Iterator<File> files = fileCollection.iterator();
1135        while (files.hasNext())
1136        {
1137            ConfigurationParser parser =
1138                new ConfigurationParser(files.next(), System.getProperties());
1139
1140            try
1141            {
1142                parser.parse(configuration);
1143            }
1144            finally
1145            {
1146                parser.close();
1147            }
1148        }
1149
1150        // Make sure the code is processed. Gradle has already checked that it
1151        // was necessary.
1152        configuration.lastModified = Long.MAX_VALUE;
1153
1154        return configuration;
1155    }
1156
1157
1158    // Small utility methods.
1159
1160    /**
1161     * Extends the given class path with the given filtered input or output
1162     * files.
1163     */
1164    private ClassPath extendClassPath(ClassPath classPath,
1165                                      Object    files,
1166                                      Map       filterArgs,
1167                                      boolean   output)
1168    {
1169        ConfigurableFileCollection fileCollection = getProject().files(files);
1170
1171        if (classPath == null)
1172        {
1173            classPath = new ClassPath();
1174        }
1175
1176        Iterator fileIterator = fileCollection.iterator();
1177        while (fileIterator.hasNext())
1178        {
1179            File file = (File)fileIterator.next();
1180            if (output || file.exists())
1181            {
1182                // Create the class path entry.
1183                ClassPathEntry classPathEntry = new ClassPathEntry(file, output);
1184
1185                // Add any filters to the class path entry.
1186                if (filterArgs != null)
1187                {
1188                    classPathEntry.setFilter(ListUtil.commaSeparatedList((String)filterArgs.get("filter")));
1189                    classPathEntry.setApkFilter(ListUtil.commaSeparatedList((String)filterArgs.get("apkfilter")));
1190                    classPathEntry.setJarFilter(ListUtil.commaSeparatedList((String)filterArgs.get("jarfilter")));
1191                    classPathEntry.setAarFilter(ListUtil.commaSeparatedList((String)filterArgs.get("aarfilter")));
1192                    classPathEntry.setWarFilter(ListUtil.commaSeparatedList((String)filterArgs.get("warfilter")));
1193                    classPathEntry.setEarFilter(ListUtil.commaSeparatedList((String)filterArgs.get("earfilter")));
1194                    classPathEntry.setZipFilter(ListUtil.commaSeparatedList((String)filterArgs.get("zipfilter")));
1195                }
1196
1197                classPath.add(classPathEntry);
1198            }
1199        }
1200
1201        return classPath;
1202    }
1203
1204
1205    /**
1206     * Creates specifications to keep classes and class members, based on the
1207     * given parameters.
1208     */
1209    private KeepClassSpecification createKeepClassSpecification(boolean allowShrinking,
1210                                                                boolean markClasses,
1211                                                                boolean markConditionally,
1212                                                                Map     keepArgs,
1213                                                                String  classSpecificationString)
1214    throws ParseException
1215    {
1216        ClassSpecification classSpecification =
1217            createClassSpecification(classSpecificationString);
1218
1219        return
1220            createKeepClassSpecification(allowShrinking,
1221                                         markClasses,
1222                                         markConditionally,
1223                                         keepArgs,
1224                                         classSpecification);
1225    }
1226
1227
1228    /**
1229     * Creates specifications to keep classes and class members, based on the
1230     * given parameters.
1231     */
1232    private KeepClassSpecification createKeepClassSpecification(boolean allowShrinking,
1233                                                                boolean markClasses,
1234                                                                boolean markConditionally,
1235                                                                Map     classSpecificationArgs,
1236                                                                Closure classMembersClosure)
1237    throws ParseException
1238    {
1239        ClassSpecification classSpecification =
1240            createClassSpecification(classSpecificationArgs,
1241                                     classMembersClosure);
1242        return
1243            createKeepClassSpecification(allowShrinking,
1244                                         markClasses,
1245                                         markConditionally,
1246                                         classSpecificationArgs,
1247                                         classSpecification);
1248    }
1249
1250
1251    /**
1252     * Creates specifications to keep classes and class members, based on the
1253     * given parameters.
1254     */
1255    private KeepClassSpecification createKeepClassSpecification(boolean            allowShrinking,
1256                                                                boolean            markClasses,
1257                                                                boolean            markConditionally,
1258                                                                Map                keepArgs,
1259                                                                ClassSpecification classSpecification)
1260    {
1261        return
1262            new KeepClassSpecification(markClasses,
1263                                       markConditionally,
1264                                       retrieveBoolean(keepArgs, "includedescriptorclasses", false),
1265                                       retrieveBoolean(keepArgs, "allowshrinking",           allowShrinking),
1266                                       retrieveBoolean(keepArgs, "allowoptimization",        false),
1267                                       retrieveBoolean(keepArgs, "allowobfuscation",         false),
1268                                       classSpecification);
1269    }
1270
1271
1272    /**
1273     * Creates specifications to keep classes and class members, based on the
1274     * given ProGuard-style class specification.
1275     */
1276    private ClassSpecification createClassSpecification(String classSpecificationString)
1277    throws ParseException
1278    {
1279        try
1280        {
1281            ConfigurationParser parser =
1282                new ConfigurationParser(new String[] { classSpecificationString }, null);
1283
1284            try
1285            {
1286                return parser.parseClassSpecificationArguments();
1287            }
1288            finally
1289            {
1290                parser.close();
1291            }
1292        }
1293        catch (IOException e)
1294        {
1295            throw new ParseException(e.getMessage());
1296        }
1297    }
1298
1299
1300    /**
1301     * Creates a specification of classes and class members, based on the
1302     * given parameters.
1303     */
1304    private ClassSpecification createClassSpecification(Map     classSpecificationArgs,
1305                                                        Closure classMembersClosure)
1306    throws ParseException
1307    {
1308        // Extract the arguments.
1309        String access            = (String)classSpecificationArgs.get("access");
1310        String annotation        = (String)classSpecificationArgs.get("annotation");
1311        String type              = (String)classSpecificationArgs.get("type");
1312        String name              = (String)classSpecificationArgs.get("name");
1313        String extendsAnnotation = (String)classSpecificationArgs.get("extendsannotation");
1314        String extends_          = (String)classSpecificationArgs.get("extends");
1315        if (extends_ == null)
1316        {
1317            extends_             = (String)classSpecificationArgs.get("implements");
1318        }
1319
1320        // Create the class specification.
1321        ClassSpecification classSpecification =
1322            new ClassSpecification(null,
1323                                   requiredClassAccessFlags(true, access, type),
1324                                   requiredClassAccessFlags(false, access, type),
1325                                   annotation        != null ? ClassUtil.internalType(annotation)        : null,
1326                                   name              != null ? ClassUtil.internalClassName(name)         : null,
1327                                   extendsAnnotation != null ? ClassUtil.internalType(extendsAnnotation) : null,
1328                                   extends_          != null ? ClassUtil.internalClassName(extends_)     : null);
1329
1330        // Initialize the class specification with its closure.
1331        if (classMembersClosure != null)
1332        {
1333            // Temporarily remember the class specification, so we can add
1334            // class member specifications.
1335            this.classSpecification = classSpecification;
1336            classMembersClosure.call(classSpecification);
1337            this.classSpecification = null;
1338        }
1339
1340        return classSpecification;
1341    }
1342
1343
1344    /**
1345     * Parses the class access flags that must be set (or not), based on the
1346     * given ProGuard-style flag specification.
1347     */
1348    private int requiredClassAccessFlags(boolean set,
1349                                         String  access,
1350                                         String  type)
1351    throws ParseException
1352    {
1353        int accessFlags = 0;
1354
1355        if (access != null)
1356        {
1357            StringTokenizer tokenizer = new StringTokenizer(access, " ,");
1358            while (tokenizer.hasMoreTokens())
1359            {
1360                String token = tokenizer.nextToken();
1361
1362                if (token.startsWith("!") ^ set)
1363                {
1364                    String strippedToken = token.startsWith("!") ?
1365                        token.substring(1) :
1366                        token;
1367
1368                    int accessFlag =
1369                        strippedToken.equals(JavaConstants.ACC_PUBLIC)     ? ClassConstants.ACC_PUBLIC      :
1370                        strippedToken.equals(JavaConstants.ACC_FINAL)      ? ClassConstants.ACC_FINAL       :
1371                        strippedToken.equals(JavaConstants.ACC_ABSTRACT)   ? ClassConstants.ACC_ABSTRACT    :
1372                        strippedToken.equals(JavaConstants.ACC_SYNTHETIC)  ? ClassConstants.ACC_SYNTHETIC   :
1373                        strippedToken.equals(JavaConstants.ACC_ANNOTATION) ? ClassConstants.ACC_ANNOTATTION :
1374                                                                             0;
1375
1376                    if (accessFlag == 0)
1377                    {
1378                        throw new ParseException("Incorrect class access modifier ["+strippedToken+"]");
1379                    }
1380
1381                    accessFlags |= accessFlag;
1382                }
1383            }
1384        }
1385
1386        if (type != null && (type.startsWith("!") ^ set))
1387        {
1388            int accessFlag =
1389                type.equals("class")                           ? 0                            :
1390                type.equals(      JavaConstants.ACC_INTERFACE) ||
1391                type.equals("!" + JavaConstants.ACC_INTERFACE) ? ClassConstants.ACC_INTERFACE :
1392                type.equals(      JavaConstants.ACC_ENUM)      ||
1393                type.equals("!" + JavaConstants.ACC_ENUM)      ? ClassConstants.ACC_ENUM      :
1394                                                                 -1;
1395            if (accessFlag == -1)
1396            {
1397                throw new ParseException("Incorrect class type ["+type+"]");
1398            }
1399
1400            accessFlags |= accessFlag;
1401        }
1402
1403        return accessFlags;
1404    }
1405
1406
1407    /**
1408     * Creates a specification of class members, based on the given parameters.
1409     */
1410    private MemberSpecification createMemberSpecification(boolean isMethod,
1411                                                          boolean isConstructor,
1412                                                          Map     classSpecificationArgs)
1413    throws ParseException
1414    {
1415        // Extract the arguments.
1416        String access            = (String)classSpecificationArgs.get("access");
1417        String type              = (String)classSpecificationArgs.get("type");
1418        String annotation        = (String)classSpecificationArgs.get("annotation");
1419        String name              = (String)classSpecificationArgs.get("name");
1420        String parameters        = (String)classSpecificationArgs.get("parameters");
1421
1422        // Perform some basic conversions and checks on the attributes.
1423        if (annotation != null)
1424        {
1425            annotation = ClassUtil.internalType(annotation);
1426        }
1427
1428        if (isMethod)
1429        {
1430            if (isConstructor)
1431            {
1432                if (type != null)
1433                {
1434                    throw new ParseException("Type attribute not allowed in constructor specification ["+type+"]");
1435                }
1436
1437                if (parameters != null)
1438                {
1439                    type = JavaConstants.TYPE_VOID;
1440                }
1441
1442                name = ClassConstants.METHOD_NAME_INIT;
1443            }
1444            else if ((type != null) ^ (parameters != null))
1445            {
1446                throw new ParseException("Type and parameters attributes must always be present in combination in method specification");
1447            }
1448        }
1449        else
1450        {
1451            if (parameters != null)
1452            {
1453                throw new ParseException("Parameters attribute not allowed in field specification ["+parameters+"]");
1454            }
1455        }
1456
1457        List parameterList = ListUtil.commaSeparatedList(parameters);
1458
1459        String descriptor =
1460            parameters != null ? ClassUtil.internalMethodDescriptor(type, parameterList) :
1461            type       != null ? ClassUtil.internalType(type)                            :
1462                                 null;
1463
1464        return new MemberSpecification(requiredMemberAccessFlags(true,  access),
1465                                       requiredMemberAccessFlags(false, access),
1466                                       annotation,
1467                                       name,
1468                                       descriptor);
1469    }
1470
1471
1472    /**
1473     * Parses the class member access flags that must be set (or not), based on
1474     * the given ProGuard-style flag specification.
1475     */
1476    private int requiredMemberAccessFlags(boolean set,
1477                                          String  access)
1478    throws ParseException
1479    {
1480        int accessFlags = 0;
1481
1482        if (access != null)
1483        {
1484            StringTokenizer tokenizer = new StringTokenizer(access, " ,");
1485            while (tokenizer.hasMoreTokens())
1486            {
1487                String token = tokenizer.nextToken();
1488
1489                if (token.startsWith("!") ^ set)
1490                {
1491                    String strippedToken = token.startsWith("!") ?
1492                        token.substring(1) :
1493                        token;
1494
1495                    int accessFlag =
1496                        strippedToken.equals(JavaConstants.ACC_PUBLIC)       ? ClassConstants.ACC_PUBLIC       :
1497                        strippedToken.equals(JavaConstants.ACC_PRIVATE)      ? ClassConstants.ACC_PRIVATE      :
1498                        strippedToken.equals(JavaConstants.ACC_PROTECTED)    ? ClassConstants.ACC_PROTECTED    :
1499                        strippedToken.equals(JavaConstants.ACC_STATIC)       ? ClassConstants.ACC_STATIC       :
1500                        strippedToken.equals(JavaConstants.ACC_FINAL)        ? ClassConstants.ACC_FINAL        :
1501                        strippedToken.equals(JavaConstants.ACC_SYNCHRONIZED) ? ClassConstants.ACC_SYNCHRONIZED :
1502                        strippedToken.equals(JavaConstants.ACC_VOLATILE)     ? ClassConstants.ACC_VOLATILE     :
1503                        strippedToken.equals(JavaConstants.ACC_TRANSIENT)    ? ClassConstants.ACC_TRANSIENT    :
1504                        strippedToken.equals(JavaConstants.ACC_BRIDGE)       ? ClassConstants.ACC_BRIDGE       :
1505                        strippedToken.equals(JavaConstants.ACC_VARARGS)      ? ClassConstants.ACC_VARARGS      :
1506                        strippedToken.equals(JavaConstants.ACC_NATIVE)       ? ClassConstants.ACC_NATIVE       :
1507                        strippedToken.equals(JavaConstants.ACC_ABSTRACT)     ? ClassConstants.ACC_ABSTRACT     :
1508                        strippedToken.equals(JavaConstants.ACC_STRICT)       ? ClassConstants.ACC_STRICT       :
1509                        strippedToken.equals(JavaConstants.ACC_SYNTHETIC)    ? ClassConstants.ACC_SYNTHETIC    :
1510                                                                               0;
1511
1512                    if (accessFlag == 0)
1513                    {
1514                        throw new ParseException("Incorrect class member access modifier ["+strippedToken+"]");
1515                    }
1516
1517                    accessFlags |= accessFlag;
1518                }
1519            }
1520        }
1521
1522        return accessFlags;
1523    }
1524
1525
1526    /**
1527     * Retrieves a specified boolean flag from the given map.
1528     */
1529    private boolean retrieveBoolean(Map args, String name, boolean defaultValue)
1530    {
1531        if (args == null)
1532        {
1533            return defaultValue;
1534        }
1535
1536        Object arg = args.get(name);
1537
1538        return arg == null ? defaultValue : ((Boolean)arg).booleanValue();
1539    }
1540
1541
1542    /**
1543     * Adds the given class specification to the given list, creating a new list
1544     * if necessary.
1545     */
1546    private List extendClassSpecifications(List               classSpecifications,
1547                                           ClassSpecification classSpecification)
1548    {
1549        if (classSpecifications == null)
1550        {
1551            classSpecifications = new ArrayList();
1552        }
1553
1554        classSpecifications.add(classSpecification);
1555
1556        return classSpecifications;
1557    }
1558
1559
1560    /**
1561     * Adds the given class specifications to the given list, creating a new
1562     * list if necessary.
1563     */
1564    private List extendClassSpecifications(List classSpecifications,
1565                                           List additionalClassSpecifications)
1566    {
1567        if (additionalClassSpecifications != null)
1568        {
1569            if (classSpecifications == null)
1570            {
1571                classSpecifications = new ArrayList();
1572            }
1573
1574            classSpecifications.addAll(additionalClassSpecifications);
1575        }
1576
1577        return classSpecifications;
1578    }
1579
1580
1581    /**
1582     * Adds the given filter to the given list, creating a new list if
1583     * necessary.
1584     */
1585    private List extendFilter(List   filter,
1586                              String filterString)
1587    {
1588        return extendFilter(filter, filterString, false);
1589    }
1590
1591
1592    /**
1593     * Adds the given filter to the given list, creating a new list if
1594     * necessary. External class names are converted to internal class names,
1595     * if requested.
1596     */
1597    private List extendFilter(List    filter,
1598                              String  filterString,
1599                              boolean convertExternalClassNames)
1600    {
1601        if (filter == null)
1602        {
1603            filter = new ArrayList();
1604        }
1605
1606        if (filterString == null)
1607        {
1608            // Clear the filter to keep all names.
1609            filter.clear();
1610        }
1611        else
1612        {
1613            if (convertExternalClassNames)
1614            {
1615                filterString = ClassUtil.internalClassName(filterString);
1616            }
1617
1618            // Append the filter.
1619            filter.addAll(ListUtil.commaSeparatedList(filterString));
1620        }
1621
1622        return filter;
1623    }
1624}
1625