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 */ 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 */ 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 */ 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 180 private Bucket(Pattern pattern) { 181 this.pattern = pattern; 182 } 183 184 private boolean matches(String path) { 185 return pattern.matcher(path).find(); 186 } 187 } 188 } 189