View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.plugins.shade.resource;
20  
21  import java.io.BufferedReader;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.InputStreamReader;
25  import java.io.OutputStreamWriter;
26  import java.io.Writer;
27  import java.text.SimpleDateFormat;
28  import java.util.Date;
29  import java.util.LinkedHashMap;
30  import java.util.LinkedHashSet;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.Set;
34  import java.util.TreeSet;
35  import java.util.jar.JarEntry;
36  import java.util.jar.JarOutputStream;
37  
38  import org.apache.maven.plugins.shade.relocation.Relocator;
39  
40  /**
41   * Merges <code>META-INF/NOTICE.TXT</code> files.
42   */
43  public class ApacheNoticeResourceTransformer extends AbstractCompatibilityTransformer {
44      Set<String> entries = new LinkedHashSet<>();
45  
46      Map<String, Set<String>> organizationEntries = new LinkedHashMap<>();
47  
48      String projectName = ""; // MSHADE-101 :: NullPointerException when projectName is missing
49  
50      boolean addHeader = true;
51  
52      String preamble1 = "// ------------------------------------------------------------------\n"
53              + "// NOTICE file corresponding to the section 4d of The Apache License,\n"
54              + "// Version 2.0, in this case for ";
55  
56      String preamble2 = "\n// ------------------------------------------------------------------\n";
57  
58      String preamble3 = "This product includes software developed at\n";
59  
60      // defaults overridable via config in pom
61      String organizationName = "The Apache Software Foundation";
62  
63      String organizationURL = "http://www.apache.org/";
64  
65      String inceptionYear = "2006";
66  
67      String copyright;
68  
69      /**
70       * The file encoding of the <code>NOTICE</code> file.
71       */
72      String encoding;
73  
74      private long time = Long.MIN_VALUE;
75  
76      private static final String NOTICE_PATH = "META-INF/NOTICE";
77  
78      private static final String NOTICE_TXT_PATH = "META-INF/NOTICE.txt";
79  
80      private static final String NOTICE_MD_PATH = "META-INF/NOTICE.md";
81  
82      @Override
83      public boolean canTransformResource(String resource) {
84          return NOTICE_PATH.equalsIgnoreCase(resource)
85                  || NOTICE_TXT_PATH.equalsIgnoreCase(resource)
86                  || NOTICE_MD_PATH.equalsIgnoreCase(resource);
87      }
88  
89      @Override
90      public void processResource(String resource, InputStream is, List<Relocator> relocators, long time)
91              throws IOException {
92          if (entries.isEmpty()) {
93              String year = new SimpleDateFormat("yyyy").format(new Date());
94              if (!inceptionYear.equals(year)) {
95                  year = inceptionYear + "-" + year;
96              }
97  
98              // add headers
99              if (addHeader) {
100                 entries.add(preamble1 + projectName + preamble2);
101             } else {
102                 entries.add("");
103             }
104             // fake second entry, we'll look for a real one later
105             entries.add(projectName + "\nCopyright " + year + " " + organizationName + "\n");
106             entries.add(preamble3 + organizationName + " (" + organizationURL + ").\n");
107         }
108 
109         BufferedReader reader;
110         if (encoding != null && !encoding.isEmpty()) {
111             reader = new BufferedReader(new InputStreamReader(is, encoding));
112         } else {
113             reader = new BufferedReader(new InputStreamReader(is));
114         }
115 
116         String line = reader.readLine();
117         StringBuilder sb = new StringBuilder();
118         Set<String> currentOrg = null;
119         int lineCount = 0;
120         while (line != null) {
121             String trimedLine = line.trim();
122 
123             if (!trimedLine.startsWith("//")) {
124                 if (trimedLine.length() > 0) {
125                     if (trimedLine.startsWith("- ")) {
126                         // resource-bundle 1.3 mode
127                         if (lineCount == 1
128                                 && sb.toString().contains("This product includes/uses software(s) developed by")) {
129                             currentOrg = organizationEntries.get(sb.toString().trim());
130                             if (currentOrg == null) {
131                                 currentOrg = new TreeSet<>();
132                                 organizationEntries.put(sb.toString().trim(), currentOrg);
133                             }
134                             sb = new StringBuilder();
135                         } else if (sb.length() > 0 && currentOrg != null) {
136                             currentOrg.add(sb.toString());
137                             sb = new StringBuilder();
138                         }
139                     }
140                     sb.append(line).append("\n");
141                     lineCount++;
142                 } else {
143                     String ent = sb.toString();
144                     if (ent.startsWith(projectName) && ent.contains("Copyright ")) {
145                         copyright = ent;
146                     }
147                     if (currentOrg == null) {
148                         entries.add(ent);
149                     } else {
150                         currentOrg.add(ent);
151                     }
152                     sb = new StringBuilder();
153                     lineCount = 0;
154                     currentOrg = null;
155                 }
156             }
157 
158             line = reader.readLine();
159         }
160         if (sb.length() > 0) {
161             if (currentOrg == null) {
162                 entries.add(sb.toString());
163             } else {
164                 currentOrg.add(sb.toString());
165             }
166         }
167         if (time > this.time) {
168             this.time = time;
169         }
170     }
171 
172     @Override
173     public boolean hasTransformedResource() {
174         return true;
175     }
176 
177     @Override
178     public void modifyOutputStream(JarOutputStream jos) throws IOException {
179         JarEntry jarEntry = new JarEntry(NOTICE_PATH);
180         jarEntry.setTime(time);
181         jos.putNextEntry(jarEntry);
182 
183         Writer writer;
184         if (encoding != null && !encoding.isEmpty()) {
185             writer = new OutputStreamWriter(jos, encoding);
186         } else {
187             writer = new OutputStreamWriter(jos);
188         }
189 
190         int count = 0;
191         for (String line : entries) {
192             ++count;
193             if (line.equals(copyright) && count != 2) {
194                 continue;
195             }
196 
197             if (count == 2 && copyright != null) {
198                 writer.write(copyright);
199                 writer.write('\n');
200             } else {
201                 writer.write(line);
202                 writer.write('\n');
203             }
204             if (count == 3) {
205                 // do org stuff
206                 for (Map.Entry<String, Set<String>> entry : organizationEntries.entrySet()) {
207                     writer.write(entry.getKey());
208                     writer.write('\n');
209                     for (String l : entry.getValue()) {
210                         writer.write(l);
211                     }
212                     writer.write('\n');
213                 }
214             }
215         }
216 
217         writer.flush();
218 
219         entries.clear();
220     }
221 }