Adding graph traversal to find cycles; adding debug mode for ConflictTracker.
[jpf-core.git] / src / main / gov / nasa / jpf / util / JPFSiteUtils.java
1 /*
2  * Copyright (C) 2014, United States Government, as represented by the
3  * Administrator of the National Aeronautics and Space Administration.
4  * All rights reserved.
5  *
6  * The Java Pathfinder core (jpf-core) platform is licensed under the
7  * Apache License, Version 2.0 (the "License"); you may not use this file except
8  * in compliance 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, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and 
16  * limitations under the License.
17  */
18
19 package gov.nasa.jpf.util;
20
21 import java.io.BufferedReader;
22 import java.io.File;
23 import java.io.FileNotFoundException;
24 import java.io.FileReader;
25 import java.io.IOException;
26 import java.io.PrintWriter;
27 import java.io.Reader;
28 import java.util.ArrayList;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33
34 /**
35  * utility class for JPF site configuration related functions. This is partially redundant to Config since
36  * it is used during bootstrapping, and gov.nasa.jpf.Config might not be in the classpath yet. It mostly
37  * differs in terms of key/value expansion, which is only partially supported here.
38  * 
39  * NOTE - this class is not allowed to use any JPF specific types
40  */
41 public class JPFSiteUtils {
42
43   //--- preparse support - we need this if we use app properties to locat lower level property files
44
45   static Pattern keyValPattern = Pattern.compile("^[ \t]*([^# \t][^ \t]*)[ \t]*=[ \t]*(.+?)[ \t]*$");
46
47   /**
48    * minimal parsing - only local key, system property and and config_path expansion
49    * NOTE this stops after finding the key, and it doesn't add the file to the 'sources'
50    */
51   public static String getMatchFromFile (String pathName, String lookupKey){
52     String value = null;
53     Pattern lookupPattern = Pattern.compile(lookupKey);
54
55     File propFile = new File(pathName);
56     if (!propFile.isFile()){
57       return null;
58     }
59
60     HashMap<String, String> map = new HashMap<String, String>();
61     String dir = propFile.getParent();
62     if (dir == null) {
63       dir = ".";
64     }
65     map.put("config_path", dir);
66
67     try {
68       FileReader fr = new FileReader(propFile);
69       BufferedReader br = new BufferedReader(fr);
70
71       for (String line = br.readLine(); line != null; line = br.readLine()) {
72         Matcher m = keyValPattern.matcher(line);
73         if (m.matches()) {
74           String key = m.group(1);
75           String val = m.group(2);
76
77           val = expandLocal(val, map);
78
79           if ((key.length() > 0) && (val.length() > 0)) {
80             // check for continuation lines
81             if (val.charAt(val.length() - 1) == '\\') {
82               val = val.substring(0, val.length() - 1).trim();
83               for (line = br.readLine(); line != null; line = br.readLine()) {
84                 line = line.trim();
85                 int len = line.length();
86                 if ((len > 0) && (line.charAt(len - 1) == '\\')) {
87                   line = line.substring(0, line.length() - 1).trim();
88                   val += expandLocal(line, map);
89                 } else {
90                   val += expandLocal(line, map);
91                   break;
92                 }
93               }
94             }
95
96             Matcher lookupMatcher = lookupPattern.matcher(key);
97             if (lookupMatcher.matches()) {
98               value = val;
99               break;
100             } else {
101               if (key.charAt(key.length() - 1) == '+') {
102                 key = key.substring(0, key.length() - 1);
103                 String v = map.get(key);
104                 if (v != null) {
105                   val = v + val;
106                 }
107               } else if (key.charAt(0) == '+') {
108                 key = key.substring(1);
109                 String v = map.get(key);
110                 if (v != null) {
111                   val = val + v;
112                 }
113               }
114               map.put(key, val);
115             }
116           }
117         }
118       }
119       br.close();
120
121     } catch (FileNotFoundException fnfx) {
122       return null;
123     } catch (IOException iox) {
124       return null;
125     }
126
127     return value;
128   }
129
130   /**
131    * this returns the contents of a config source in-order, without expanding values or keys
132    */
133   public static List<Pair<String,String>> getRawEntries (Reader reader) throws IOException {
134     ArrayList<Pair<String,String>> list = new ArrayList<Pair<String,String>>();
135     BufferedReader br = new BufferedReader(reader);
136     
137     for (String line = br.readLine(); line != null; line = br.readLine()) {
138       Matcher m = keyValPattern.matcher(line);
139       if (m.matches()) {
140         String key = m.group(1);
141         String val = m.group(2);
142         
143         if ((key.length() > 0) && (val.length() > 0)) {
144           // check for continuation lines
145           if (val.charAt(val.length() - 1) == '\\') {
146             val = val.substring(0, val.length() - 1).trim();
147             for (line = br.readLine(); line != null; line = br.readLine()) {
148               line = line.trim();
149               int len = line.length();
150               if ((len > 0) && (line.charAt(len - 1) == '\\')) {
151                 line = line.substring(0, line.length() - 1).trim();
152                 val += line;
153               } else {
154                 val += line;
155                 break;
156               }
157             }
158           }
159           
160           list.add( new Pair<String,String>(key,val));
161         }
162       }
163     }
164     
165     return list;
166   }
167   
168   // simple non-recursive, local key and system property expander
169   private static String expandLocal (String s, HashMap<String,String> map) {
170     int i, j = 0;
171     if (s == null || s.length() == 0) {
172       return s;
173     }
174
175     while ((i = s.indexOf("${", j)) >= 0) {
176       if ((j = s.indexOf('}', i)) > 0) {
177         String k = s.substring(i + 2, j);
178         String v = null;
179
180         if (map != null){
181           v = map.get(k);
182         }
183         if (v == null){
184           v = System.getProperty(k);
185         }
186
187         if (v != null) {
188           s = s.substring(0, i) + v + s.substring(j + 1, s.length());
189           j = i + v.length();
190         } else {
191           s = s.substring(0, i) + s.substring(j + 1, s.length());
192           j = i;
193         }
194       }
195     }
196
197     return s;
198   }
199
200   public static File getCoreDir (File siteProps){
201     if (siteProps != null){
202       String path = getMatchFromFile(siteProps.getAbsolutePath(), "jpf-core");
203       if (path != null) {
204         File coreDir = new File(path);
205         if (coreDir.isDirectory()) {
206           return coreDir;
207         }
208       }
209     }
210     return null;
211   }
212   
213   public static File getSiteCoreDir (String[] args){
214     File siteProps = getSiteProperties( args);    
215     return getCoreDir( siteProps);
216   }
217
218   /**
219    * get location of jpf-core from site.properties
220    * @return null if it doesn't exist
221    */
222   public static File getSiteCoreDir() {
223     File siteProps = getStandardSiteProperties();
224     return getCoreDir( siteProps);
225   }
226
227   /**
228    * find project properties (jpf.properties) from current dir
229    */
230   public static File getCurrentProjectProperties() {
231     File d = new File(System.getProperty("user.dir"));
232     do {
233       File f = new File(d, "jpf.properties");
234       if (f.isFile()){
235         return f;
236       }
237       d = d.getParentFile();
238     } while (d != null);
239
240     return null;
241   }
242
243
244   static Pattern idPattern = Pattern.compile("^[ \t]*([^# \t][^ \t]*)[ \t]*=[ \t]*\\$\\{config_path\\}");
245
246   static String projectId;
247
248   /**
249    * look for a "<id> = ${config_path}" entry in current dir/jpf.properties
250    * this looks recursively upwards
251    * @return null if no jpf.properties found
252    */
253   public static String getCurrentProjectId (){
254     if (projectId == null) {
255       File propFile = getCurrentProjectProperties();
256
257       if (propFile != null) {
258         try {
259           FileReader fr = new FileReader(propFile);
260           BufferedReader br = new BufferedReader(fr);
261
262           for (String line = br.readLine(); line != null; line = br.readLine()) {
263             Matcher m = idPattern.matcher(line);
264             if (m.matches()) {
265               projectId = m.group(1);
266             }
267           }
268           br.close();
269
270         } catch (FileNotFoundException fnfx) {
271           return null;
272         } catch (IOException iox) {
273           return null;
274         }
275       }
276     }
277
278     return projectId;
279   }
280   
281   public static boolean isFreeArg (String a){
282     return ((a != null) && (a.length() > 0) && a.charAt(0) != '+' && a.charAt(0) != '-');
283   }
284   
285   public static File getSiteProperties (String[] args){
286     //--- 1. check for a +site=<path> argument up to first free arg
287     for (int i=0; i<args.length; i++){
288       String a = args[i];
289       if (!isFreeArg(a)){
290         if (a.startsWith("+site=")) {
291           String path = a.substring(6).trim();
292           return new File(path);
293         }
294       } else {
295         break;
296       }
297     }
298     
299     //--- 2. check if the first free arg is an application property file (*.jpf), and it contains a 'site=..' setting
300     for (int i=0; i<args.length; i++){
301       String a = args[i];
302       if (isFreeArg(a)){
303         if (a.matches("[^+-].*\\.jpf")) {
304           String path = getMatchFromFile(a, "site");
305           if (path != null) {
306             return new File(path);
307           }
308         }
309         break;
310       }
311     }
312     
313     //--- 3. finally, check upwards from the current dir up to the home dir 
314     return JPFSiteUtils.getStandardSiteProperties();
315   } 
316   
317   /**
318    * locate the site.properties. Start with the current dir, go upwards until the
319    * user.home is reached. If site.properties isn't found there, look for '.jpf' and
320    * 'jpf' dirs within the home dir. If no site.properties is found there either, give up
321    */
322   public static File getStandardSiteProperties(){    
323     String userDir = System.getProperty("user.dir");
324     File dir = new File(userDir);
325     for (; dir != null; dir = dir.getParentFile()) {
326       File f = new File(dir, "site.properties");
327       if (f.isFile()) {
328         return f;
329       }
330     }
331
332     String[] jpfDirCandidates = { ".jpf", "jpf" };
333     String userHome = System.getProperty("user.home");
334     
335     for (String jpfDir : jpfDirCandidates){
336       dir = new File(userHome, jpfDir);
337       if (dir.isDirectory()) {
338         File f = new File(dir, "site.properties");
339         if (f.isFile()) {
340           return f;
341         }
342       }
343     }
344     
345     return null;
346   }
347
348   public static String getGlobalSitePropertiesPath() {
349     String userHome = System.getProperty("user.home");
350     String globalPath = userHome + File.separator + ".jpf"
351          + File.separator + "site.properties";
352     return globalPath;
353   }
354   
355   public static List<Pair<String,String>> getRawEntries (File siteProps){
356     FileReader fr = null;
357     if (siteProps.isFile()) {
358       try {
359         fr = new FileReader(siteProps);
360         List<Pair<String,String>> entries = getRawEntries(fr);
361         fr.close();
362         
363         return entries;
364
365       } catch (IOException iox) {
366       } finally {
367         try { fr.close(); } catch (IOException _ignore){}
368       }
369     }    
370     
371     return new ArrayList<Pair<String,String>>();
372   }
373     
374   /**
375    * this returns a list of all the project ids in the 'extensions' entries (also
376    * handles accumulated 'extensions+=.." entries
377    */
378   public static List<String> getExtensions (List<Pair<String,String>> entries){
379     ArrayList<String> list = new ArrayList<String>();
380     
381     for (Pair<String,String> p : entries){
382       if (p._1.startsWith("extensions")){
383         for (String pid : p._2.split("[,;]")){
384           pid = pid.trim();
385           if (pid.charAt(0) == '$'){
386             pid = pid.substring(2, pid.length()-1);
387             list.add( pid);
388           }
389         }
390       }
391     }
392     
393     return list;
394   }
395   
396   
397   public static boolean addProject (File siteProps, String projectId, File projectDir, boolean isExt){
398     List<Pair<String,String>> entries = getRawEntries(siteProps);
399     List<String> extensions = getExtensions(entries);
400
401     if ("jpf-core".equals(projectId)){ // jpf-core always has to be in the extensions list
402       isExt = true;
403     }
404     
405     try {
406       FileUtils.ensureDirs(siteProps);
407       String projectPath = FileUtils.asCanonicalUserPathName(projectDir.getAbsolutePath());
408
409       PrintWriter pw = new PrintWriter(siteProps);
410
411       pw.println("# auto-generated JPF site properties");
412       pw.println();
413
414       boolean alreadyThere = false;
415       for (Pair<String, String> e : entries) {
416         if (!"extensions".equals(e._1)) {
417
418           pw.print(e._1);
419           pw.print(" = ");
420
421           if (projectId.equals(e._1)) {
422             alreadyThere = true;
423             // Hmm, not sure its best to use absolute pathnames here (e.g. when doing local site installs)
424             pw.println(projectPath);
425             
426             // check if we have to update extensions
427             if (extensions.contains(projectId)){
428               if (!isExt){
429                 extensions.remove(projectId);
430               }
431             } else {
432               extensions.add(projectId);
433             }
434             
435           } else {
436             pw.println(e._2);
437           }
438         }
439       }
440
441       if (!alreadyThere) {
442         if (isExt) {
443           extensions.add(projectId);
444         }
445
446         pw.print(projectId);
447         pw.print(" = ");
448         pw.println(projectPath);
449       }
450
451       pw.println();
452       pw.print("extensions = ");
453
454       boolean isFirst = true;
455       for (String e : extensions) {
456         if (isFirst) {
457           isFirst = false;
458         } else {
459           pw.print(',');          
460         }
461         pw.print("${");
462         pw.print(e);
463         pw.print('}');
464       }
465       
466       pw.println();
467       pw.close();
468
469     } catch (IOException iox) {
470       iox.printStackTrace();
471       return false;
472     }
473     
474     return true;
475   }
476 }