1   package com.atlassian.maven.plugins.amps.util;
2   
3   import java.io.File;
4   import java.io.FileInputStream;
5   import java.io.FileOutputStream;
6   import java.io.IOException;
7   import java.io.InputStream;
8   import java.io.OutputStream;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Properties;
12  
13  import org.apache.commons.io.FileUtils;
14  import org.apache.commons.io.IOUtils;
15  import org.apache.maven.plugin.MojoExecutionException;
16  import org.apache.maven.plugin.logging.Log;
17  import com.google.common.base.Preconditions;
18  
19  public class ConfigFileUtils
20  {
21      public static void replace(List<File> files, List<Replacement> replacements, boolean inverted, Log log) throws MojoExecutionException
22      {
23          for(File file : files)
24          {
25              replace(file, replacements, inverted, log);
26          }
27      }
28  
29      /**
30       * @param cfgFile the file
31       * @param replacements the list of keys to replace with values
32       * @param inverted if you want to swap values with keys. Be aware that the list is processed in order,
33       * so that if 2 keys have the same value, the first key will be chosen. The Replacement records with
34       * reversible set to false will not be reversed. Default: false.
35       */
36      public static void replace(File cfgFile, List<Replacement> replacements, boolean inverted, Log log) throws MojoExecutionException
37      {
38          if (!cfgFile.exists())
39          {
40              return;
41          }
42          try
43          {
44              String config = FileUtils.readFileToString(cfgFile);
45              if (!inverted)
46              {
47                  for (Replacement replacement : replacements)
48                  {
49                      if (replacement.applyWhenUnzipping())
50                      {
51                          config = config.replace(replacement.getKey(), replacement.getValue());
52                      }
53                  }
54              }
55              else
56              {
57                  for (Replacement replacement : replacements)
58                  {
59                      if (replacement.isReversible())
60                      {
61                          config = config.replace(replacement.getValue(), replacement.getKey());
62                      }
63                  }
64              }
65              FileUtils.writeStringToFile(cfgFile, config);
66          }
67          catch (IOException ex)
68          {
69              throw new MojoExecutionException("Unable to replace " + cfgFile, ex);
70          }
71      }
72  
73      public static void replaceAll(File cfgFile, String pattern, String replacement) throws MojoExecutionException
74      {
75          if (!cfgFile.exists())
76          {
77              return;
78          }
79          try
80          {
81              String config = FileUtils.readFileToString(cfgFile);
82              config = config.replaceAll(pattern, replacement); // obeys regex
83              FileUtils.writeStringToFile(cfgFile, config);
84          }
85          catch (IOException ex)
86          {
87              throw new MojoExecutionException("Unable to replace " + cfgFile, ex);
88          }
89      }
90  
91      public static void setProperties(File propertiesFile, Map<String, String> newProperties) throws MojoExecutionException
92      {
93          InputStream in = null;
94          OutputStream out = null;
95  
96          try
97          {
98              in = new FileInputStream(propertiesFile);
99  
100             Properties props = new Properties();
101             props.load(in);
102             in.close();
103             in = null;
104 
105             for (Map.Entry<String, String> e : newProperties.entrySet())
106             {
107                 props.setProperty(e.getKey(), e.getValue());
108             }
109 
110             out = new FileOutputStream(propertiesFile);
111             props.store(out, "Processed by AMPS");
112             out.close();
113             out = null;
114         }
115         catch (IOException ioe)
116         {
117             IOUtils.closeQuietly(in);
118             IOUtils.closeQuietly(out);
119         }
120     }
121 
122     /**
123      * Represents a replacement in a configuration file or set of files.
124      */
125     public static class Replacement
126     {
127         private final String key;
128         private final String value;
129 
130         /** Replace the key with the value when unzipping a home. This is the normal meaning of
131          * a replacement, {@code key -> value} */
132         private final boolean applyWhenUnzipping;
133 
134         /** Detect the value and replace it with the key when zipping a home directory */
135         private final boolean reversible;
136 
137         /**
138          * Represents a key to be replaced in the configuration files.
139          *
140          * <p/>
141          * <b>Important</b>: If your value is short, such as "/", "", "true", "false", please set reversible=false.
142          * When zipping a home, config files are parsed and everything is replaced back with keys, such as %PRODUCT_HOME_DIR%.
143          * If you provide a string with false positives, you may parametrise too many variables.
144          *
145          *
146          * @param key the key to be replaced. Must not be null.
147          * @param value the value to be replaced. Must not be null. <b>Important</b>: If short, such as / or "", please set reversible=false.
148          */
149         public Replacement(String key, String value)
150         {
151             this(key, value, true);
152         }
153 
154         /**
155          * Represents a key to be replaced in the configuration files.
156          * @param key the key to be replaced. Must not be null.
157          * @param value the value to be replaced. Must not be null.
158          * @param reversible true if the value should be replaced with the key before
159          * preparing a snapshot. Default is true. Use false when:<ul>
160          * <li>the value is non-unique, e.g. "%BAMBOO_ENABLED% = true" should not be reversible.</li>
161          * <li>we only support the value for legacy, but we wouldn't re-wrap a snapshot with this key</li>
162          * </ul>
163          */
164         public Replacement(String key, String value, boolean reversible)
165         {
166             this(key, value, true, reversible);
167         }
168 
169         /**
170          * @param key the key, never null
171          * @param value the value, never null
172          * @param applyWhenUnzipping apply when unzipping a home. Defaults to true.
173          * @param applyWhenZipping apply when zipping a home. Defaults to true.
174          */
175         Replacement(String key, String value, boolean applyWhenUnzipping, boolean applyWhenZipping)
176         {
177             Preconditions.checkArgument(key != null, "key must not be null");
178             Preconditions.checkArgument(value != null, "value must not be null");
179             this.key = key;
180             this.value = value;
181             this.applyWhenUnzipping = applyWhenUnzipping;
182             this.reversible = applyWhenZipping;
183         }
184 
185         public static Replacement onlyWhenCreatingSnapshot(String key, String value)
186         {
187             return new Replacement(key, value, true, false);
188         }
189 
190         /**
191          * @return the key to be replaced. Never null.
192          */
193         public String getKey()
194         {
195             return key;
196         }
197 
198         /**
199          * @return the value. Never null.
200          */
201         public String getValue()
202         {
203             return value;
204         }
205 
206         public boolean isReversible()
207         {
208             return reversible;
209         }
210 
211         public boolean applyWhenUnzipping()
212         {
213             return applyWhenUnzipping;
214         }
215 
216         @Override
217         public String toString()
218         {
219             String operation;
220 
221             if (applyWhenUnzipping && reversible)
222             {
223                 operation = " <-> ";
224             }
225             else if (applyWhenUnzipping && !reversible)
226             {
227                 operation = " -> ";
228             }
229             else if (!applyWhenUnzipping && reversible)
230             {
231                 operation = " <- ";
232             }
233             else // !applyWhenUnzipping && !reversible
234             {
235                 operation = " (nop) ";
236             }
237 
238             return key + operation + value;
239         }
240     }
241 }