1 // Copyright 2008 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.tools
.info
;
6 import java
.io
.FileFilter
;
7 import java
.net
.MalformedURLException
;
8 import java
.net
.URISyntaxException
;
10 import java
.util
.ArrayList
;
11 import java
.util
.Collection
;
12 import java
.util
.Collections
;
13 import java
.util
.List
;
14 import java
.util
.SortedMap
;
15 import java
.util
.TreeMap
;
18 * Retrieves installation information for the App Engine SDK.
21 public class SdkInfo
{
23 public static final String SDK_ROOT_PROPERTY
= "appengine.sdk.root";
25 private static final String DEFAULT_SERVER
= "appengine.google.com";
27 private static boolean isInitialized
= false;
28 private static File sdkRoot
= null;
29 private static List
<File
> sharedLibFiles
= null;
30 private static List
<URL
> sharedLibs
= null;
31 private static List
<File
> userLibFiles
= null;
32 private static List
<URL
> userLibs
= null;
33 private static SortedMap
<String
, OptionalLib
> optionalUserLibsByName
= null;
34 private static SortedMap
<String
, OptionalLib
> optionalToolsLibsByName
= null;
35 private static boolean isDevAppServerTest
;
37 private static final FileFilter NO_HIDDEN_FILES
= new FileFilter() {
39 public boolean accept(File file
) {
40 return !file
.isHidden();
44 static List
<URL
> toURLs(List
<File
> files
) {
45 List
<URL
> urls
= new ArrayList
<URL
>(files
.size());
46 for (File file
: files
) {
47 urls
.add(toURL(file
));
52 @SuppressWarnings({"deprecation"})
53 static URL
toURL(File file
) {
56 } catch (MalformedURLException e
) {
57 throw new RuntimeException("Unable get a URL from " + file
, e
);
61 static List
<File
> getLibs(File sdkRoot
, String libSubDir
) {
62 return getLibs(sdkRoot
, libSubDir
, false);
65 static List
<File
> getLibsRecursive(File sdkRoot
, String libSubDir
) {
66 return getLibs(sdkRoot
, libSubDir
, true);
69 private static List
<File
> getLibs(File sdkRoot
, String libSubDir
, boolean recursive
) {
70 File subDir
= new File(sdkRoot
, "lib" + File
.separator
+ libSubDir
);
72 if (!subDir
.exists()) {
73 throw new IllegalArgumentException("Unable to find " + subDir
.getAbsolutePath());
76 List
<File
> libs
= new ArrayList
<File
>();
77 getLibs(subDir
, libs
, recursive
);
81 private static void getLibs(File dir
, List
<File
> list
, boolean recursive
) {
82 for (File f
: listFiles(dir
)) {
83 if (f
.isDirectory() && recursive
) {
84 getLibs(f
, list
, recursive
);
86 if (f
.getName().endsWith(".jar")) {
93 private static File
findSdkRoot() {
94 String explicitRootString
= System
.getProperty(SDK_ROOT_PROPERTY
);
95 if (explicitRootString
!= null) {
96 return new File(explicitRootString
);
99 URL codeLocation
= SdkInfo
.class.getProtectionDomain().getCodeSource().getLocation();
100 String msg
= "Unable to discover the Google App Engine SDK root. This code should be loaded " +
101 "from the SDK directory, but was instead loaded from " + codeLocation
+ ". Specify " +
102 "-Dappengine.sdk.root to override the SDK location.";
105 libDir
= new File(codeLocation
.toURI());
106 } catch (URISyntaxException e
) {
107 libDir
= new File(codeLocation
.getFile());
109 while (!libDir
.getName().equals("lib")) {
110 libDir
= libDir
.getParentFile();
111 if (libDir
== null) {
112 throw new RuntimeException(msg
);
115 return libDir
.getParentFile();
119 * Returns the full paths of all shared libraries for the SDK. Users
120 * should compile against these libraries, but <b>not</b> bundle them
121 * with their web application. These libraries are already included
122 * as part of the App Engine runtime.
124 public static List
<URL
> getSharedLibs() {
130 * Returns the paths of all shared libraries for the SDK.
132 public static List
<File
> getSharedLibFiles() {
134 return sharedLibFiles
;
138 * @deprecated Use {@link #getOptionalUserLibs()} instead.
141 public static List
<URL
> getUserLibs() {
147 * @deprecated Use {@link #getOptionalUserLibs()} instead.
150 public static List
<File
> getUserLibFiles() {
156 * Returns all optional user libraries for the SDK. Users who opt to use
157 * these libraries should both compile against and deploy them in the
158 * WEB-INF/lib folder of their web applications.
160 public static Collection
<OptionalLib
> getOptionalUserLibs() {
162 return optionalUserLibsByName
.values();
165 public static OptionalLib
getOptionalUserLib(String name
) {
167 return optionalUserLibsByName
.get(name
);
171 * Returns all optional tools libraries for the SDK.
173 public static Collection
<OptionalLib
> getOptionalToolsLibs() {
175 return optionalToolsLibsByName
.values();
178 public static OptionalLib
getOptionalToolsLib(String name
) {
180 return optionalToolsLibsByName
.get(name
);
184 * Returns the path to the root of the SDK.
186 public static File
getSdkRoot() {
192 * Explicitly specifies the path to the root of the SDK. This takes
193 * precedence over the {@code appengine.sdk.root} system property,
194 * but must be called before any other methods in this class.
196 * @throws IllegalStateException If any other methods have already
199 public synchronized static void setSdkRoot(File root
) {
200 if (isInitialized
&& !sdkRoot
.equals(root
)) {
201 throw new IllegalStateException("Cannot set SDK root after initialization has occurred.");
206 public static Version
getLocalVersion() {
207 return new LocalVersionFactory(getUserLibFiles()).getVersion();
210 public static String
getDefaultServer() {
211 return DEFAULT_SERVER
;
215 * If {@code true}, the testing jar will be added to the shared libs. This
216 * is intended for use by frameworks that want to run tests inside the
217 * isolated classloader.
219 * @param val Whether or the testing jar should be included on the shared
222 public static void includeTestingJarOnSharedPath(boolean val
) {
223 isDevAppServerTest
= val
;
225 private synchronized static void init() {
226 if (!isInitialized
) {
227 if (sdkRoot
== null) {
228 sdkRoot
= findSdkRoot();
230 sharedLibFiles
= determineSharedLibFiles();
231 sharedLibs
= Collections
.unmodifiableList(toURLs(sharedLibFiles
));
232 if (new File(sdkRoot
, "lib" + File
.separator
+ "user").isDirectory()) {
233 userLibFiles
= Collections
.unmodifiableList(getLibsRecursive(sdkRoot
, "user"));
235 userLibFiles
= Collections
.emptyList();
237 userLibs
= Collections
.unmodifiableList(toURLs(userLibFiles
));
238 optionalUserLibsByName
= Collections
.unmodifiableSortedMap(determineOptionalUserLibs());
239 optionalToolsLibsByName
= Collections
.unmodifiableSortedMap(determineOptionalToolsLibs());
240 isInitialized
= true;
245 * Optional user libs reside under <sdk_root>/lib/opt/user. Each top-level
246 * directory under this path identifies an optional user library, and each
247 * sub-directory for a specific library represents a version of that library.
248 * So for example we could have:
249 * lib/opt/user/mylib1/v1/mylib.jar
250 * lib/opt/user/mylib1/v2/mylib.jar
251 * lib/opt/user/mylib2/v1/mylib.jar
252 * lib/opt/user/mylib2/v2/mylib.jar
254 * @return A {@link SortedMap} from the name of the library to an
255 * {@link OptionalLib} that describes the library. The map is sorted by
258 private static SortedMap
<String
, OptionalLib
> determineOptionalUserLibs() {
259 return determineOptionalLibs(new File(sdkRoot
, "lib/opt/user"));
263 * Optional tools libs reside under <sdk_root>/lib/opt/tools. Each top-level
264 * directory under this path identifies an optional tools library, and each
265 * sub-directory for a specific library represents a version of that library.
266 * So for example we could have:
267 * lib/opt/tools/mylib1/v1/mylib.jar
268 * lib/opt/tools/mylib1/v2/mylib.jar
269 * lib/opt/tools/mylib2/v1/mylib.jar
270 * lib/opt/tools/mylib2/v2/mylib.jar
272 * @return A {@link SortedMap} from the name of the library to an
273 * {@link OptionalLib} that describes the library. The map is sorted by
276 private static SortedMap
<String
, OptionalLib
> determineOptionalToolsLibs() {
277 return determineOptionalLibs(new File(sdkRoot
, "lib/opt/tools"));
280 private static SortedMap
<String
, OptionalLib
> determineOptionalLibs(File root
) {
281 SortedMap
<String
, OptionalLib
> map
= new TreeMap
<String
, OptionalLib
>();
282 for (File libDir
: listFiles(root
)) {
283 SortedMap
<String
, List
<File
>> filesByVersion
= new TreeMap
<String
, List
<File
>>();
284 for (File version
: listFiles(libDir
)) {
285 List
<File
> filesForVersion
= new ArrayList
<File
>();
286 getLibs(version
, filesForVersion
, true);
287 filesByVersion
.put(version
.getName(), filesForVersion
);
289 String description
= "";
290 OptionalLib userLib
=
291 new OptionalLib(libDir
.getName(), description
, filesByVersion
);
292 map
.put(userLib
.getName(), userLib
);
297 private static List
<File
> determineSharedLibFiles() {
298 List
<File
> sharedLibs
= getLibsRecursive(sdkRoot
, "shared");
299 if (isDevAppServerTest
) {
300 sharedLibs
.addAll(getLibsRecursive(sdkRoot
, "testing"));
302 return Collections
.unmodifiableList(sharedLibs
);
306 * A version of {@link File#listFiles()} that never returns {@code null}.
307 * Historically this has been an issue, since listFiles() can return null if
308 * the parent directory does not exist or is not readable.
310 * @param dir The directory whose files we want to list.
311 * @return The contents of the provided directory.
313 static File
[] listFiles(File dir
) {
314 File
[] files
= dir
.listFiles(NO_HIDDEN_FILES
);