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.ResourceTransformer;
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 ResourceTransformer
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 void processResource( final String resource, final InputStream is, final List<Relocator> relocators,
78                                   long time )
79              throws IOException
80      {
81          final Properties p = new Properties();
82          p.load( is );
83          properties.add( p );
84          if ( time > this.time )
85          {
86              this.time = time;        
87          }
88      }
89  
90      @Override
91      public boolean hasTransformedResource()
92      {
93          return !properties.isEmpty();
94      }
95  
96      @Override
97      public void modifyOutputStream( JarOutputStream os )
98          throws IOException
99      {
100         if ( properties.isEmpty() )
101         {
102             return;
103         }
104 
105         final Properties out = mergeProperties( sortProperties() );
106         if ( ordinalKey != null )
107         {
108             out.remove( ordinalKey );
109         }
110         if ( alreadyMergedKey != null )
111         {
112             out.remove( alreadyMergedKey );
113         }
114         JarEntry jarEntry = new JarEntry( resource );
115         jarEntry.setTime( time );
116         os.putNextEntry( jarEntry );
117         final BufferedWriter writer = new SkipPropertiesDateLineWriter(
118                 new OutputStreamWriter( new NoCloseOutputStream( os ), StandardCharsets.ISO_8859_1 ) );
119         out.store( writer, " Merged by maven-shade-plugin (" + getClass().getName() + ")" );
120         writer.close();
121         os.closeEntry();
122     }
123 
124     public void setReverseOrder( final boolean reverseOrder )
125     {
126         this.reverseOrder = reverseOrder;
127     }
128 
129     public void setResource( final String resource )
130     {
131         this.resource = resource;
132     }
133 
134     public void setOrdinalKey( final String ordinalKey )
135     {
136         this.ordinalKey = ordinalKey;
137     }
138 
139     public void setDefaultOrdinal( final int defaultOrdinal )
140     {
141         this.defaultOrdinal = defaultOrdinal;
142     }
143 
144     public void setAlreadyMergedKey( final String alreadyMergedKey )
145     {
146         this.alreadyMergedKey = alreadyMergedKey;
147     }
148 
149     private List<Properties> sortProperties()
150     {
151         final List<Properties> sortedProperties = new ArrayList<>();
152         boolean foundMaster = false;
153         for ( final Properties current : properties )
154         {
155             if ( alreadyMergedKey != null )
156             {
157                 final String master = current.getProperty( alreadyMergedKey );
158                 if ( Boolean.parseBoolean( master ) )
159                 {
160                     if ( foundMaster )
161                     {
162                         throw new IllegalStateException(
163                                 "Ambiguous merged values: " + sortedProperties + ", " + current );
164                     }
165                     foundMaster = true;
166                     sortedProperties.clear();
167                     sortedProperties.add( current );
168                 }
169             }
170             if ( !foundMaster )
171             {
172                 final int configOrder = getConfigurationOrdinal( current );
173 
174                 int i;
175                 for ( i = 0; i < sortedProperties.size(); i++ )
176                 {
177                     int listConfigOrder = getConfigurationOrdinal( sortedProperties.get( i ) );
178                     if ( ( !reverseOrder && listConfigOrder > configOrder )
179                             || ( reverseOrder && listConfigOrder < configOrder ) )
180                     {
181                         break;
182                     }
183                 }
184                 sortedProperties.add( i, current );
185             }
186         }
187         return sortedProperties;
188     }
189 
190     private int getConfigurationOrdinal( final Properties p )
191     {
192         if ( ordinalKey == null )
193         {
194             return defaultOrdinal;
195         }
196         final String configOrderString = p.getProperty( ordinalKey );
197         if ( configOrderString != null && configOrderString.length() > 0 )
198         {
199             return Integer.parseInt( configOrderString );
200         }
201         return defaultOrdinal;
202     }
203 
204     private static Properties mergeProperties( final List<Properties> sortedProperties )
205     {
206         final Properties mergedProperties = new SortedProperties();
207         for ( final Properties p : sortedProperties )
208         {
209             mergedProperties.putAll( p );
210         }
211         return mergedProperties;
212     }
213 }