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      public boolean canTransformResource(String resource) {
83          return NOTICE_PATH.equalsIgnoreCase(resource)
84                  || NOTICE_TXT_PATH.equalsIgnoreCase(resource)
85                  || NOTICE_MD_PATH.equalsIgnoreCase(resource);
86      }
87  
88      public void processResource(String resource, InputStream is, List<Relocator> relocators, long time)
89              throws IOException {
90          if (entries.isEmpty()) {
91              String year = new SimpleDateFormat("yyyy").format(new Date());
92              if (!inceptionYear.equals(year)) {
93                  year = inceptionYear + "-" + year;
94              }
95  
96              // add headers
97              if (addHeader) {
98                  entries.add(preamble1 + projectName + preamble2);
99              } else {
100                 entries.add("");
101             }
102             // fake second entry, we'll look for a real one later
103             entries.add(projectName + "\nCopyright " + year + " " + organizationName + "\n");
104             entries.add(preamble3 + organizationName + " (" + organizationURL + ").\n");
105         }
106 
107         BufferedReader reader;
108         if (encoding != null && !encoding.isEmpty()) {
109             reader = new BufferedReader(new InputStreamReader(is, encoding));
110         } else {
111             reader = new BufferedReader(new InputStreamReader(is));
112         }
113 
114         String line = reader.readLine();
115         StringBuilder sb = new StringBuilder();
116         Set<String> currentOrg = null;
117         int lineCount = 0;
118         while (line != null) {
119             String trimedLine = line.trim();
120 
121             if (!trimedLine.startsWith("//")) {
122                 if (trimedLine.length() > 0) {
123                     if (trimedLine.startsWith("- ")) {
124                         // resource-bundle 1.3 mode
125                         if (lineCount == 1
126                                 && sb.toString().contains("This product includes/uses software(s) developed by")) {
127                             currentOrg = organizationEntries.get(sb.toString().trim());
128                             if (currentOrg == null) {
129                                 currentOrg = new TreeSet<>();
130                                 organizationEntries.put(sb.toString().trim(), currentOrg);
131                             }
132                             sb = new StringBuilder();
133                         } else if (sb.length() > 0 && currentOrg != null) {
134                             currentOrg.add(sb.toString());
135                             sb = new StringBuilder();
136                         }
137                     }
138                     sb.append(line).append("\n");
139                     lineCount++;
140                 } else {
141                     String ent = sb.toString();
142                     if (ent.startsWith(projectName) && ent.contains("Copyright ")) {
143                         copyright = ent;
144                     }
145                     if (currentOrg == null) {
146                         entries.add(ent);
147                     } else {
148                         currentOrg.add(ent);
149                     }
150                     sb = new StringBuilder();
151                     lineCount = 0;
152                     currentOrg = null;
153                 }
154             }
155 
156             line = reader.readLine();
157         }
158         if (sb.length() > 0) {
159             if (currentOrg == null) {
160                 entries.add(sb.toString());
161             } else {
162                 currentOrg.add(sb.toString());
163             }
164         }
165         if (time > this.time) {
166             this.time = time;
167         }
168     }
169 
170     public boolean hasTransformedResource() {
171         return true;
172     }
173 
174     public void modifyOutputStream(JarOutputStream jos) throws IOException {
175         JarEntry jarEntry = new JarEntry(NOTICE_PATH);
176         jarEntry.setTime(time);
177         jos.putNextEntry(jarEntry);
178 
179         Writer writer;
180         if (encoding != null && !encoding.isEmpty()) {
181             writer = new OutputStreamWriter(jos, encoding);
182         } else {
183             writer = new OutputStreamWriter(jos);
184         }
185 
186         int count = 0;
187         for (String line : entries) {
188             ++count;
189             if (line.equals(copyright) && count != 2) {
190                 continue;
191             }
192 
193             if (count == 2 && copyright != null) {
194                 writer.write(copyright);
195                 writer.write('\n');
196             } else {
197                 writer.write(line);
198                 writer.write('\n');
199             }
200             if (count == 3) {
201                 // do org stuff
202                 for (Map.Entry<String, Set<String>> entry : organizationEntries.entrySet()) {
203                     writer.write(entry.getKey());
204                     writer.write('\n');
205                     for (String l : entry.getValue()) {
206                         writer.write(l);
207                     }
208                     writer.write('\n');
209                 }
210             }
211         }
212 
213         writer.flush();
214 
215         entries.clear();
216     }
217 }