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.properties;
20  
21  import java.io.BufferedWriter;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.OutputStreamWriter;
25  import java.nio.charset.StandardCharsets;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.Objects;
29  import java.util.Properties;
30  import java.util.jar.JarEntry;
31  import java.util.jar.JarOutputStream;
32  
33  import org.apache.maven.plugins.shade.relocation.Relocator;
34  import org.apache.maven.plugins.shade.resource.ReproducibleResourceTransformer;
35  import org.apache.maven.plugins.shade.resource.properties.io.NoCloseOutputStream;
36  import org.apache.maven.plugins.shade.resource.properties.io.SkipPropertiesDateLineWriter;
37  
38  /**
39   * Enables to merge a set of properties respecting priority between them.
40   *
41   * @since 3.2.2
42   */
43  public class PropertiesTransformer implements ReproducibleResourceTransformer {
44      private String resource;
45      private String alreadyMergedKey;
46      private String ordinalKey;
47      private int defaultOrdinal;
48      private boolean reverseOrder;
49      private long time = Long.MIN_VALUE;
50  
51      private final List<Properties> properties = new ArrayList<>();
52  
53      public PropertiesTransformer() {
54          // no-op
55      }
56  
57      protected PropertiesTransformer(
58              final String resource, final String ordinalKey, final int defaultOrdinal, final boolean reversed) {
59          this.resource = resource;
60          this.ordinalKey = ordinalKey;
61          this.defaultOrdinal = defaultOrdinal;
62          this.reverseOrder = reversed;
63      }
64  
65      @Override
66      public boolean canTransformResource(final String resource) {
67          return Objects.equals(resource, this.resource);
68      }
69  
70      @Override
71      public final void processResource(String resource, InputStream is, List<Relocator> relocators) throws IOException {
72          processResource(resource, is, relocators, 0);
73      }
74  
75      @Override
76      public void processResource(
77              final String resource, final InputStream is, final List<Relocator> relocators, long time)
78              throws IOException {
79          final Properties p = new Properties();
80          p.load(is);
81          properties.add(p);
82          if (time > this.time) {
83              this.time = time;
84          }
85      }
86  
87      @Override
88      public boolean hasTransformedResource() {
89          return !properties.isEmpty();
90      }
91  
92      @Override
93      public void modifyOutputStream(JarOutputStream os) throws IOException {
94          if (properties.isEmpty()) {
95              return;
96          }
97  
98          final Properties out = mergeProperties(sortProperties());
99          if (ordinalKey != null) {
100             out.remove(ordinalKey);
101         }
102         if (alreadyMergedKey != null) {
103             out.remove(alreadyMergedKey);
104         }
105         JarEntry jarEntry = new JarEntry(resource);
106         jarEntry.setTime(time);
107         os.putNextEntry(jarEntry);
108         final BufferedWriter writer = new SkipPropertiesDateLineWriter(
109                 new OutputStreamWriter(new NoCloseOutputStream(os), StandardCharsets.ISO_8859_1));
110         out.store(writer, " Merged by maven-shade-plugin (" + getClass().getName() + ")");
111         writer.close();
112         os.closeEntry();
113     }
114 
115     public void setReverseOrder(final boolean reverseOrder) {
116         this.reverseOrder = reverseOrder;
117     }
118 
119     public void setResource(final String resource) {
120         this.resource = resource;
121     }
122 
123     public void setOrdinalKey(final String ordinalKey) {
124         this.ordinalKey = ordinalKey;
125     }
126 
127     public void setDefaultOrdinal(final int defaultOrdinal) {
128         this.defaultOrdinal = defaultOrdinal;
129     }
130 
131     public void setAlreadyMergedKey(final String alreadyMergedKey) {
132         this.alreadyMergedKey = alreadyMergedKey;
133     }
134 
135     private List<Properties> sortProperties() {
136         final List<Properties> sortedProperties = new ArrayList<>();
137         boolean foundMaster = false;
138         for (final Properties current : properties) {
139             if (alreadyMergedKey != null) {
140                 final String master = current.getProperty(alreadyMergedKey);
141                 if (Boolean.parseBoolean(master)) {
142                     if (foundMaster) {
143                         throw new IllegalStateException(
144                                 "Ambiguous merged values: " + sortedProperties + ", " + current);
145                     }
146                     foundMaster = true;
147                     sortedProperties.clear();
148                     sortedProperties.add(current);
149                 }
150             }
151             if (!foundMaster) {
152                 final int configOrder = getConfigurationOrdinal(current);
153 
154                 int i;
155                 for (i = 0; i < sortedProperties.size(); i++) {
156                     int listConfigOrder = getConfigurationOrdinal(sortedProperties.get(i));
157                     if ((!reverseOrder && listConfigOrder > configOrder)
158                             || (reverseOrder && listConfigOrder < configOrder)) {
159                         break;
160                     }
161                 }
162                 sortedProperties.add(i, current);
163             }
164         }
165         return sortedProperties;
166     }
167 
168     private int getConfigurationOrdinal(final Properties p) {
169         if (ordinalKey == null) {
170             return defaultOrdinal;
171         }
172         final String configOrderString = p.getProperty(ordinalKey);
173         if (configOrderString != null && configOrderString.length() > 0) {
174             return Integer.parseInt(configOrderString);
175         }
176         return defaultOrdinal;
177     }
178 
179     private static Properties mergeProperties(final List<Properties> sortedProperties) {
180         final Properties mergedProperties = new SortedProperties();
181         for (final Properties p : sortedProperties) {
182             mergedProperties.putAll(p);
183         }
184         return mergedProperties;
185     }
186 }