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