View Javadoc
1   package org.apache.maven.plugins.shade.resource.properties;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.BufferedWriter;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStreamWriter;
26  import java.nio.charset.StandardCharsets;
27  import java.util.ArrayList;
28  import java.util.List;
29  import java.util.Objects;
30  import java.util.Properties;
31  import java.util.jar.JarEntry;
32  import java.util.jar.JarOutputStream;
33  
34  import org.apache.maven.plugins.shade.relocation.Relocator;
35  import org.apache.maven.plugins.shade.resource.ReproducibleResourceTransformer;
36  import org.apache.maven.plugins.shade.resource.properties.io.NoCloseOutputStream;
37  import org.apache.maven.plugins.shade.resource.properties.io.SkipPropertiesDateLineWriter;
38  
39  /**
40   * Enables to merge a set of properties respecting priority between them.
41   *
42   * @since 3.2.2
43   */
44  public class PropertiesTransformer
45      implements ReproducibleResourceTransformer
46  {
47      private String resource;
48      private String alreadyMergedKey;
49      private String ordinalKey;
50      private int defaultOrdinal;
51      private boolean reverseOrder;
52      private long time = Long.MIN_VALUE;
53  
54      private final List<Properties> properties = new ArrayList<>();
55  
56      public PropertiesTransformer()
57      {
58          // no-op
59      }
60  
61      protected PropertiesTransformer( final String resource, final String ordinalKey,
62                                       final int defaultOrdinal, final boolean reversed )
63      {
64          this.resource = resource;
65          this.ordinalKey = ordinalKey;
66          this.defaultOrdinal = defaultOrdinal;
67          this.reverseOrder = reversed;
68      }
69  
70      @Override
71      public boolean canTransformResource( final String resource )
72      {
73          return Objects.equals( resource, this.resource );
74      }
75  
76      @Override
77      public final void processResource( String resource, InputStream is, List<Relocator> relocators )
78          throws IOException
79      {
80          processResource( resource, is, relocators, 0 );
81      }
82  
83      @Override
84      public void processResource( final String resource, final InputStream is, final List<Relocator> relocators,
85                                   long time )
86              throws IOException
87      {
88          final Properties p = new Properties();
89          p.load( is );
90          properties.add( p );
91          if ( time > this.time )
92          {
93              this.time = time;        
94          }
95      }
96  
97      @Override
98      public boolean hasTransformedResource()
99      {
100         return !properties.isEmpty();
101     }
102 
103     @Override
104     public void modifyOutputStream( JarOutputStream os )
105         throws IOException
106     {
107         if ( properties.isEmpty() )
108         {
109             return;
110         }
111 
112         final Properties out = mergeProperties( sortProperties() );
113         if ( ordinalKey != null )
114         {
115             out.remove( ordinalKey );
116         }
117         if ( alreadyMergedKey != null )
118         {
119             out.remove( alreadyMergedKey );
120         }
121         JarEntry jarEntry = new JarEntry( resource );
122         jarEntry.setTime( time );
123         os.putNextEntry( jarEntry );
124         final BufferedWriter writer = new SkipPropertiesDateLineWriter(
125                 new OutputStreamWriter( new NoCloseOutputStream( os ), StandardCharsets.ISO_8859_1 ) );
126         out.store( writer, " Merged by maven-shade-plugin (" + getClass().getName() + ")" );
127         writer.close();
128         os.closeEntry();
129     }
130 
131     public void setReverseOrder( final boolean reverseOrder )
132     {
133         this.reverseOrder = reverseOrder;
134     }
135 
136     public void setResource( final String resource )
137     {
138         this.resource = resource;
139     }
140 
141     public void setOrdinalKey( final String ordinalKey )
142     {
143         this.ordinalKey = ordinalKey;
144     }
145 
146     public void setDefaultOrdinal( final int defaultOrdinal )
147     {
148         this.defaultOrdinal = defaultOrdinal;
149     }
150 
151     public void setAlreadyMergedKey( final String alreadyMergedKey )
152     {
153         this.alreadyMergedKey = alreadyMergedKey;
154     }
155 
156     private List<Properties> sortProperties()
157     {
158         final List<Properties> sortedProperties = new ArrayList<>();
159         boolean foundMaster = false;
160         for ( final Properties current : properties )
161         {
162             if ( alreadyMergedKey != null )
163             {
164                 final String master = current.getProperty( alreadyMergedKey );
165                 if ( Boolean.parseBoolean( master ) )
166                 {
167                     if ( foundMaster )
168                     {
169                         throw new IllegalStateException(
170                                 "Ambiguous merged values: " + sortedProperties + ", " + current );
171                     }
172                     foundMaster = true;
173                     sortedProperties.clear();
174                     sortedProperties.add( current );
175                 }
176             }
177             if ( !foundMaster )
178             {
179                 final int configOrder = getConfigurationOrdinal( current );
180 
181                 int i;
182                 for ( i = 0; i < sortedProperties.size(); i++ )
183                 {
184                     int listConfigOrder = getConfigurationOrdinal( sortedProperties.get( i ) );
185                     if ( ( !reverseOrder && listConfigOrder > configOrder )
186                             || ( reverseOrder && listConfigOrder < configOrder ) )
187                     {
188                         break;
189                     }
190                 }
191                 sortedProperties.add( i, current );
192             }
193         }
194         return sortedProperties;
195     }
196 
197     private int getConfigurationOrdinal( final Properties p )
198     {
199         if ( ordinalKey == null )
200         {
201             return defaultOrdinal;
202         }
203         final String configOrderString = p.getProperty( ordinalKey );
204         if ( configOrderString != null && configOrderString.length() > 0 )
205         {
206             return Integer.parseInt( configOrderString );
207         }
208         return defaultOrdinal;
209     }
210 
211     private static Properties mergeProperties( final List<Properties> sortedProperties )
212     {
213         final Properties mergedProperties = new SortedProperties();
214         for ( final Properties p : sortedProperties )
215         {
216             mergedProperties.putAll( p );
217         }
218         return mergedProperties;
219     }
220 }