1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 import java.io.File;
18 import java.io.IOException;
19 import java.util.SortedSet;
20 import java.util.TreeMap;
21 import java.util.TreeSet;
22 import java.util.Collection;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.regex.Pattern;
26 
27 /**
28  * Generates an Eclipse project.
29  */
30 public class Eclipse {
31 
32     /**
33      * Generates an Eclipse .classpath file from the given configuration.
34      */
generateFrom(Configuration c)35     public static void generateFrom(Configuration c) throws IOException {
36         StringBuilder classpath = new StringBuilder();
37 
38         classpath.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
39                 + "<classpath>\n");
40 
41         /*
42          * If the user has a file named "path-precedence" in their project's
43          * root directory, we'll order source roots based on how they match
44          * regular expressions in that file. Source roots that match earlier
45          * patterns will come sooner in configuration file.
46          */
47         List<Pattern> patterns = new ArrayList<Pattern>();
48 
49         File precedence = new File("path-precedence");
50         if (precedence.exists()) {
51             Configuration.parseFile(precedence, patterns);
52         } else {
53             // Put ./out at the bottom by default.
54             patterns.add(Pattern.compile("^(?!out/)"));
55         }
56 
57         // Everything not matched by the user's precedence spec.
58         patterns.add(Pattern.compile(".*"));
59 
60 
61         List<Bucket> buckets = new ArrayList<Bucket>(patterns.size());
62         for (Pattern pattern : patterns) {
63             buckets.add(new Bucket(pattern));
64         }
65 
66         // Put source roots in respective buckets.
67         OUTER: for (File sourceRoot : c.sourceRoots) {
68             // Trim preceding "./" from path.
69             String path = sourceRoot.getPath().substring(2);
70 
71             for (Bucket bucket : buckets) {
72                 if (bucket.matches(path)) {
73                     bucket.sourceRoots.add(sourceRoot);
74                     continue OUTER;
75                 }
76             }
77         }
78 
79         // Output source roots to configuration file.
80         for (Bucket bucket : buckets) {
81             for (File sourceRoot : bucket.sourceRoots) {
82                 classpath.append("  <classpathentry kind=\"src\"");
83                 CharSequence excluding = constructExcluding(sourceRoot, c);
84                 if (excluding.length() > 0) {
85                     classpath.append(" excluding=\"")
86                             .append(excluding).append("\"");
87                 }
88                 classpath.append(" path=\"")
89                         .append(trimmed(sourceRoot)).append("\"/>\n");
90             }
91 
92         }
93 
94         // Output .jar entries.
95         for (File jar : c.jarFiles) {
96             classpath.append("  <classpathentry kind=\"lib\" path=\"")
97                     .append(trimmed(jar)).append("\"/>\n");
98         }
99 
100         /*
101          * Output directory. Unfortunately, Eclipse forces us to put it
102          * somewhere under the project directory.
103          */
104         classpath.append("  <classpathentry kind=\"output\" path=\""
105                 + "out/eclipse\"/>\n");
106 
107         classpath.append("</classpath>\n");
108 
109         Files.toFile(classpath.toString(), new File(".classpath"));
110     }
111 
112 
113     /**
114      * Constructs the "excluding" argument for a given source root.
115      */
constructExcluding(File sourceRoot, Configuration c)116     private static CharSequence constructExcluding(File sourceRoot,
117             Configuration c) {
118         StringBuilder classpath = new StringBuilder();
119         String path = sourceRoot.getPath();
120 
121         // Exclude nested source roots.
122         SortedSet<File> nextRoots = c.sourceRoots.tailSet(sourceRoot);
123         int count = 0;
124         for (File nextRoot : nextRoots) {
125             // The first root is this root.
126             if (count == 0) {
127                 count++;
128                 continue;
129             }
130 
131             String nextPath = nextRoot.getPath();
132             if (!nextPath.startsWith(path)) {
133                 break;
134             }
135 
136             if (count > 1) {
137                 classpath.append('|');
138             }
139             classpath.append(nextPath.substring(path.length() + 1))
140                     .append('/');
141 
142             count++;
143         }
144 
145         // Exclude excluded directories under this source root.
146         SortedSet<File> excludedDirs = c.excludedDirs.tailSet(sourceRoot);
147         for (File excludedDir : excludedDirs) {
148             String excludedPath = excludedDir.getPath();
149             if (!excludedPath.startsWith(path)) {
150                 break;
151             }
152 
153             if (count > 1) {
154                 classpath.append('|');
155             }
156             classpath.append(excludedPath.substring(path.length() + 1))
157                     .append('/');
158 
159             count++;
160         }
161 
162         return classpath;
163     }
164 
165     /**
166      * Returns the trimmed path.
167      */
trimmed(File file)168     private static String trimmed(File file) {
169         return file.getPath().substring(2);
170     }
171 
172     /**
173      * A precedence bucket for source roots.
174      */
175     private static class Bucket {
176 
177         private final Pattern pattern;
178         private final List<File> sourceRoots = new ArrayList<File>();
179 
Bucket(Pattern pattern)180         private Bucket(Pattern pattern) {
181             this.pattern = pattern;
182         }
183 
matches(String path)184         private boolean matches(String path) {
185             return pattern.matcher(path).find();
186         }
187     }
188 }
189