• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 package com.android.tools.lint.detector.api;
18 
19 import static com.android.tools.lint.detector.api.LintConstants.ANDROID_LIBRARY;
20 import static com.android.tools.lint.detector.api.LintConstants.ANDROID_LIBRARY_REFERENCE_FORMAT;
21 import static com.android.tools.lint.detector.api.LintConstants.ANDROID_MANIFEST_XML;
22 import static com.android.tools.lint.detector.api.LintConstants.ANDROID_URI;
23 import static com.android.tools.lint.detector.api.LintConstants.ATTR_MIN_SDK_VERSION;
24 import static com.android.tools.lint.detector.api.LintConstants.ATTR_PACKAGE;
25 import static com.android.tools.lint.detector.api.LintConstants.ATTR_TARGET_SDK_VERSION;
26 import static com.android.tools.lint.detector.api.LintConstants.PROGUARD_CONFIG;
27 import static com.android.tools.lint.detector.api.LintConstants.PROJECT_PROPERTIES;
28 import static com.android.tools.lint.detector.api.LintConstants.TAG_USES_SDK;
29 import static com.android.tools.lint.detector.api.LintConstants.VALUE_TRUE;
30 
31 import com.android.annotations.NonNull;
32 import com.android.annotations.Nullable;
33 import com.android.tools.lint.client.api.Configuration;
34 import com.android.tools.lint.client.api.LintClient;
35 import com.android.tools.lint.client.api.SdkInfo;
36 import com.google.common.annotations.Beta;
37 import com.google.common.base.Charsets;
38 import com.google.common.io.Closeables;
39 import com.google.common.io.Files;
40 
41 import org.w3c.dom.Document;
42 import org.w3c.dom.Element;
43 import org.w3c.dom.NodeList;
44 
45 import java.io.BufferedInputStream;
46 import java.io.File;
47 import java.io.FileInputStream;
48 import java.io.IOException;
49 import java.util.ArrayList;
50 import java.util.Collection;
51 import java.util.Collections;
52 import java.util.List;
53 import java.util.Properties;
54 import java.util.regex.Matcher;
55 import java.util.regex.Pattern;
56 
57 /**
58  * A project contains information about an Android project being scanned for
59  * Lint errors.
60  * <p>
61  * <b>NOTE: This is not a public or final API; if you rely on this be prepared
62  * to adjust your code for the next tools release.</b>
63  */
64 @Beta
65 public class Project {
66     private final LintClient mClient;
67     private final File mDir;
68     private final File mReferenceDir;
69     private Configuration mConfiguration;
70     private String mPackage;
71     private int mMinSdk = 1;
72     private int mTargetSdk = -1;
73     private boolean mLibrary;
74     private String mName;
75     private String mProguardPath;
76 
77     /** The SDK info, if any */
78     private SdkInfo mSdkInfo;
79 
80     /**
81      * If non null, specifies a non-empty list of specific files under this
82      * project which should be checked.
83      */
84     private List<File> mFiles;
85     private List<File> mJavaSourceFolders;
86     private List<File> mJavaClassFolders;
87     private List<File> mJavaLibraries;
88     private List<Project> mDirectLibraries;
89     private List<Project> mAllLibraries;
90 
91     /**
92      * Creates a new {@link Project} for the given directory.
93      *
94      * @param client the tool running the lint check
95      * @param dir the root directory of the project
96      * @param referenceDir See {@link #getReferenceDir()}.
97      * @return a new {@link Project}
98      */
99     @NonNull
create( @onNull LintClient client, @NonNull File dir, @NonNull File referenceDir)100     public static Project create(
101             @NonNull LintClient client,
102             @NonNull  File dir,
103             @NonNull File referenceDir) {
104         return new Project(client, dir, referenceDir);
105     }
106 
107     /** Creates a new Project. Use one of the factory methods to create. */
Project( @onNull LintClient client, @NonNull File dir, @NonNull File referenceDir)108     private Project(
109             @NonNull LintClient client,
110             @NonNull File dir,
111             @NonNull File referenceDir) {
112         mClient = client;
113         mDir = dir;
114         mReferenceDir = referenceDir;
115 
116         try {
117             // Read properties file and initialize library state
118             Properties properties = new Properties();
119             File propFile = new File(dir, PROJECT_PROPERTIES);
120             if (propFile.exists()) {
121                 BufferedInputStream is = new BufferedInputStream(new FileInputStream(propFile));
122                 try {
123                     properties.load(is);
124                     String value = properties.getProperty(ANDROID_LIBRARY);
125                     mLibrary = VALUE_TRUE.equals(value);
126                     mProguardPath = properties.getProperty(PROGUARD_CONFIG);
127 
128                     for (int i = 1; i < 1000; i++) {
129                         String key = String.format(ANDROID_LIBRARY_REFERENCE_FORMAT, i);
130                         String library = properties.getProperty(key);
131                         if (library == null || library.length() == 0) {
132                             // No holes in the numbering sequence is allowed
133                             break;
134                         }
135 
136                         File libraryDir = new File(dir, library).getCanonicalFile();
137 
138                         if (mDirectLibraries == null) {
139                             mDirectLibraries = new ArrayList<Project>();
140                         }
141 
142                         // Adjust the reference dir to be a proper prefix path of the
143                         // library dir
144                         File libraryReferenceDir = referenceDir;
145                         if (!libraryDir.getPath().startsWith(referenceDir.getPath())) {
146                             // Symlinks etc might have been resolved, so do those to
147                             // the reference dir as well
148                             libraryReferenceDir = libraryReferenceDir.getCanonicalFile();
149                             if (!libraryDir.getPath().startsWith(referenceDir.getPath())) {
150                                 File f = libraryReferenceDir;
151                                 while (f != null && f.getPath().length() > 0) {
152                                     if (libraryDir.getPath().startsWith(f.getPath())) {
153                                         libraryReferenceDir = f;
154                                         break;
155                                     }
156                                     f = f.getParentFile();
157                                 }
158                             }
159                         }
160 
161                         mDirectLibraries.add(client.getProject(libraryDir, libraryReferenceDir));
162                     }
163                 } finally {
164                     Closeables.closeQuietly(is);
165                 }
166             }
167         } catch (IOException ioe) {
168             client.log(ioe, "Initializing project state");
169         }
170 
171         if (mDirectLibraries != null) {
172             mDirectLibraries = Collections.unmodifiableList(mDirectLibraries);
173         } else {
174             mDirectLibraries = Collections.emptyList();
175         }
176     }
177 
178     @Override
toString()179     public String toString() {
180         return "Project [dir=" + mDir + "]";
181     }
182 
183     @Override
hashCode()184     public int hashCode() {
185         final int prime = 31;
186         int result = 1;
187         result = prime * result + ((mDir == null) ? 0 : mDir.hashCode());
188         return result;
189     }
190 
191     @Override
equals(@ullable Object obj)192     public boolean equals(@Nullable Object obj) {
193         if (this == obj)
194             return true;
195         if (obj == null)
196             return false;
197         if (getClass() != obj.getClass())
198             return false;
199         Project other = (Project) obj;
200         if (mDir == null) {
201             if (other.mDir != null)
202                 return false;
203         } else if (!mDir.equals(other.mDir))
204             return false;
205         return true;
206     }
207 
208     /**
209      * Adds the given file to the list of files which should be checked in this
210      * project. If no files are added, the whole project will be checked.
211      *
212      * @param file the file to be checked
213      */
addFile(@onNull File file)214     public void addFile(@NonNull File file) {
215         if (mFiles == null) {
216             mFiles = new ArrayList<File>();
217         }
218         mFiles.add(file);
219     }
220 
221     /**
222      * The list of files to be checked in this project. If null, the whole
223      * project should be checked.
224      *
225      * @return the subset of files to be checked, or null for the whole project
226      */
227     @Nullable
getSubset()228     public List<File> getSubset() {
229         return mFiles;
230     }
231 
232     /**
233      * Returns the list of source folders for Java source files
234      *
235      * @return a list of source folders to search for .java files
236      */
237     @NonNull
getJavaSourceFolders()238     public List<File> getJavaSourceFolders() {
239         if (mJavaSourceFolders == null) {
240             if (isAospBuildEnvironment()) {
241                 String top = getAospTop();
242                 if (mDir.getAbsolutePath().startsWith(top)) {
243                     mJavaSourceFolders = getAospJavaSourcePath();
244                     return mJavaSourceFolders;
245                 }
246             }
247 
248             mJavaSourceFolders = mClient.getJavaSourceFolders(this);
249         }
250 
251         return mJavaSourceFolders;
252     }
253 
254     /**
255      * Returns the list of output folders for class files
256      * @return a list of output folders to search for .class files
257      */
258     @NonNull
getJavaClassFolders()259     public List<File> getJavaClassFolders() {
260         if (mJavaClassFolders == null) {
261             if (isAospBuildEnvironment()) {
262                 String top = getAospTop();
263                 if (mDir.getAbsolutePath().startsWith(top)) {
264                     mJavaClassFolders = getAospJavaClassPath();
265                     return mJavaClassFolders;
266                 }
267             }
268 
269             mJavaClassFolders = mClient.getJavaClassFolders(this);
270         }
271         return mJavaClassFolders;
272     }
273 
274     /**
275      * Returns the list of Java libraries (typically .jar files) that this
276      * project depends on. Note that this refers to jar libraries, not Android
277      * library projects which are processed in a separate pass with their own
278      * source and class folders.
279      *
280      * @return a list of .jar files (or class folders) that this project depends
281      *         on.
282      */
283     @NonNull
getJavaLibraries()284     public List<File> getJavaLibraries() {
285         if (mJavaLibraries == null) {
286             // AOSP builds already merge libraries and class folders into
287             // the single classes.jar file, so these have already been processed
288             // in getJavaClassFolders.
289 
290             mJavaLibraries = mClient.getJavaLibraries(this);
291         }
292 
293         return mJavaLibraries;
294     }
295 
296     /**
297      * Returns the relative path of a given file relative to the user specified
298      * directory (which is often the project directory but sometimes a higher up
299      * directory when a directory tree is being scanned
300      *
301      * @param file the file under this project to check
302      * @return the path relative to the reference directory (often the project directory)
303      */
304     @NonNull
getDisplayPath(@onNull File file)305     public String getDisplayPath(@NonNull File file) {
306        String path = file.getPath();
307        String referencePath = mReferenceDir.getPath();
308        if (path.startsWith(referencePath)) {
309            int length = referencePath.length();
310            if (path.length() > length && path.charAt(length) == File.separatorChar) {
311                length++;
312            }
313 
314            return path.substring(length);
315        }
316 
317        return path;
318     }
319 
320     /**
321      * Returns the relative path of a given file within the current project.
322      *
323      * @param file the file under this project to check
324      * @return the path relative to the project
325      */
326     @NonNull
getRelativePath(@onNull File file)327     public String getRelativePath(@NonNull File file) {
328        String path = file.getPath();
329        String referencePath = mDir.getPath();
330        if (path.startsWith(referencePath)) {
331            int length = referencePath.length();
332            if (path.length() > length && path.charAt(length) == File.separatorChar) {
333                length++;
334            }
335 
336            return path.substring(length);
337        }
338 
339        return path;
340     }
341 
342     /**
343      * Returns the project root directory
344      *
345      * @return the dir
346      */
347     @NonNull
getDir()348     public File getDir() {
349         return mDir;
350     }
351 
352     /**
353      * Returns the original user supplied directory where the lint search
354      * started. For example, if you run lint against {@code /tmp/foo}, and it
355      * finds a project to lint in {@code /tmp/foo/dev/src/project1}, then the
356      * {@code dir} is {@code /tmp/foo/dev/src/project1} and the
357      * {@code referenceDir} is {@code /tmp/foo/}.
358      *
359      * @return the reference directory, never null
360      */
361     @NonNull
getReferenceDir()362     public File getReferenceDir() {
363         return mReferenceDir;
364     }
365 
366     /**
367      * Gets the configuration associated with this project
368      *
369      * @return the configuration associated with this project
370      */
371     @NonNull
getConfiguration()372     public Configuration getConfiguration() {
373         if (mConfiguration == null) {
374             mConfiguration = mClient.getConfiguration(this);
375         }
376         return mConfiguration;
377     }
378 
379     /**
380      * Returns the application package specified by the manifest
381      *
382      * @return the application package, or null if unknown
383      */
384     @Nullable
getPackage()385     public String getPackage() {
386         //assert !mLibrary; // Should call getPackage on the master project, not the library
387         // Assertion disabled because you might be running lint on a standalone library project.
388 
389         return mPackage;
390     }
391 
392     /**
393      * Returns the minimum API level requested by the manifest, or -1 if not
394      * specified
395      *
396      * @return the minimum API level or -1 if unknown
397      */
getMinSdk()398     public int getMinSdk() {
399         //assert !mLibrary; // Should call getMinSdk on the master project, not the library
400         // Assertion disabled because you might be running lint on a standalone library project.
401 
402         return mMinSdk;
403     }
404 
405     /**
406      * Returns the target API level specified by the manifest, or -1 if not
407      * specified
408      *
409      * @return the target API level or -1 if unknown
410      */
getTargetSdk()411     public int getTargetSdk() {
412         //assert !mLibrary; // Should call getTargetSdk on the master project, not the library
413         // Assertion disabled because you might be running lint on a standalone library project.
414 
415         return mTargetSdk;
416     }
417 
418     /**
419      * Initialized the manifest state from the given manifest model
420      *
421      * @param document the DOM document for the manifest XML document
422      */
readManifest(@onNull Document document)423     public void readManifest(@NonNull Document document) {
424         Element root = document.getDocumentElement();
425         if (root == null) {
426             return;
427         }
428 
429         mPackage = root.getAttribute(ATTR_PACKAGE);
430 
431         // Initialize minSdk and targetSdk
432         NodeList usesSdks = root.getElementsByTagName(TAG_USES_SDK);
433         if (usesSdks.getLength() > 0) {
434             Element element = (Element) usesSdks.item(0);
435 
436             String minSdk = null;
437             if (element.hasAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION)) {
438                 minSdk = element.getAttributeNS(ANDROID_URI, ATTR_MIN_SDK_VERSION);
439             }
440             if (minSdk != null) {
441                 try {
442                     mMinSdk = Integer.valueOf(minSdk);
443                 } catch (NumberFormatException e) {
444                     mMinSdk = 1;
445                 }
446             }
447 
448             String targetSdk = null;
449             if (element.hasAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION)) {
450                 targetSdk = element.getAttributeNS(ANDROID_URI, ATTR_TARGET_SDK_VERSION);
451             } else if (minSdk != null) {
452                 targetSdk = minSdk;
453             }
454             if (targetSdk != null) {
455                 try {
456                     mTargetSdk = Integer.valueOf(targetSdk);
457                 } catch (NumberFormatException e) {
458                     // TODO: Handle codenames?
459                     mTargetSdk = -1;
460                 }
461             }
462         } else if (isAospBuildEnvironment()) {
463             extractAospMinSdkVersion();
464         }
465     }
466 
467     /**
468      * Returns true if this project is an Android library project
469      *
470      * @return true if this project is an Android library project
471      */
isLibrary()472     public boolean isLibrary() {
473         return mLibrary;
474     }
475 
476     /**
477      * Returns the list of library projects referenced by this project
478      *
479      * @return the list of library projects referenced by this project, never
480      *         null
481      */
482     @NonNull
getDirectLibraries()483     public List<Project> getDirectLibraries() {
484         return mDirectLibraries;
485     }
486 
487     /**
488      * Returns the transitive closure of the library projects for this project
489      *
490      * @return the transitive closure of the library projects for this project
491      */
492     @NonNull
getAllLibraries()493     public List<Project> getAllLibraries() {
494         if (mAllLibraries == null) {
495             if (mDirectLibraries.size() == 0) {
496                 return mDirectLibraries;
497             }
498 
499             List<Project> all = new ArrayList<Project>();
500             addLibraryProjects(all);
501             mAllLibraries = all;
502         }
503 
504         return mAllLibraries;
505     }
506 
507     /**
508      * Adds this project's library project and their library projects
509      * recursively into the given collection of projects
510      *
511      * @param collection the collection to add the projects into
512      */
addLibraryProjects(@onNull Collection<Project> collection)513     private void addLibraryProjects(@NonNull Collection<Project> collection) {
514         for (Project library : mDirectLibraries) {
515             collection.add(library);
516             // Recurse
517             library.addLibraryProjects(collection);
518         }
519     }
520 
521     /**
522      * Gets the SDK info for the current project.
523      *
524      * @return the SDK info for the current project, never null
525      */
526     @NonNull
getSdkInfo()527     public SdkInfo getSdkInfo() {
528         if (mSdkInfo == null) {
529             mSdkInfo = mClient.getSdkInfo(this);
530         }
531 
532         return mSdkInfo;
533     }
534 
535     /**
536      * Gets the path to the manifest file in this project, if it exists
537      *
538      * @return the path to the manifest file, or null if it does not exist
539      */
getManifestFile()540     public File getManifestFile() {
541         File manifestFile = new File(mDir, ANDROID_MANIFEST_XML);
542         if (manifestFile.exists()) {
543             return manifestFile;
544         }
545 
546         return null;
547     }
548 
549     /**
550      * Returns the proguard path configured for this project, or null if ProGuard is
551      * not configured.
552      *
553      * @return the proguard path, or null
554      */
555     @Nullable
getProguardPath()556     public String getProguardPath() {
557         return mProguardPath;
558     }
559 
560     /**
561      * Returns the name of the project
562      *
563      * @return the name of the project, never null
564      */
565     @NonNull
getName()566     public String getName() {
567         if (mName == null) {
568             // TODO: Consider reading the name from .project (if it's an Eclipse project)
569             mName = mDir.getName();
570         }
571 
572         return mName;
573     }
574 
575     // ---------------------------------------------------------------------------
576     // Support for running lint on the AOSP source tree itself
577 
578     private static Boolean sAospBuild;
579 
580     /** Is lint running in an AOSP build environment */
isAospBuildEnvironment()581     private static boolean isAospBuildEnvironment() {
582         if (sAospBuild == null) {
583             sAospBuild = getAospTop() != null;
584         }
585 
586         return sAospBuild.booleanValue();
587     }
588 
589     /** Get the root AOSP dir, if any */
getAospTop()590     private static String getAospTop() {
591         return System.getenv("ANDROID_BUILD_TOP");   //$NON-NLS-1$
592     }
593 
594     /** Get the host out directory in AOSP, if any */
getAospHostOut()595     private static String getAospHostOut() {
596         return System.getenv("ANDROID_HOST_OUT");    //$NON-NLS-1$
597     }
598 
599     /** Get the product out directory in AOSP, if any */
getAospProductOut()600     private static String getAospProductOut() {
601         return System.getenv("ANDROID_PRODUCT_OUT"); //$NON-NLS-1$
602     }
603 
getAospJavaSourcePath()604     private List<File> getAospJavaSourcePath() {
605         List<File> sources = new ArrayList<File>(2);
606         // Normal sources
607         File src = new File(mDir, "src"); //$NON-NLS-1$
608         if (src.exists()) {
609             sources.add(src);
610         }
611 
612         // Generates sources
613         for (File dir : getIntermediateDirs()) {
614             File classes = new File(dir, "src"); //$NON-NLS-1$
615             if (classes.exists()) {
616                 sources.add(classes);
617             }
618         }
619 
620         if (sources.size() == 0) {
621             mClient.log(null,
622                     "Warning: Could not find sources or generated sources for project %1$s",
623                     getName());
624         }
625 
626         return sources;
627     }
628 
getAospJavaClassPath()629     private List<File> getAospJavaClassPath() {
630         List<File> classDirs = new ArrayList<File>(1);
631 
632         for (File dir : getIntermediateDirs()) {
633             File classes = new File(dir, "classes"); //$NON-NLS-1$
634             if (classes.exists()) {
635                 classDirs.add(classes);
636             } else {
637                 classes = new File(dir, "classes.jar"); //$NON-NLS-1$
638                 if (classes.exists()) {
639                     classDirs.add(classes);
640                 }
641             }
642         }
643 
644         if (classDirs.size() == 0) {
645             mClient.log(null,
646                     "No bytecode found: Has the project been built? (%1$s)", getName());
647         }
648 
649         return classDirs;
650     }
651 
652     /** Find the _intermediates directories for a given module name */
getIntermediateDirs()653     private List<File> getIntermediateDirs() {
654         // See build/core/definitions.mk and in particular the "intermediates-dir-for" definition
655         List<File> intermediates = new ArrayList<File>();
656 
657         // TODO: Look up the module name, e.g. LOCAL_MODULE. However,
658         // some Android.mk files do some complicated things with it - and most
659         // projects use the same module name as the directory name.
660         String moduleName = mDir.getName();
661 
662         String top = getAospTop();
663         final String[] outFolders = new String[] {
664             top + "/out/host/common/obj",             //$NON-NLS-1$
665             top + "/out/target/common/obj",           //$NON-NLS-1$
666             getAospHostOut() + "/obj",                //$NON-NLS-1$
667             getAospProductOut() + "/obj"              //$NON-NLS-1$
668         };
669         final String[] moduleClasses = new String[] {
670                 "APPS",                //$NON-NLS-1$
671                 "JAVA_LIBRARIES",      //$NON-NLS-1$
672         };
673 
674         for (String out : outFolders) {
675             assert new File(out.replace('/', File.separatorChar)).exists() : out;
676             for (String moduleClass : moduleClasses) {
677                 String path = out + '/' + moduleClass + '/' + moduleName
678                         + "_intermediates"; //$NON-NLS-1$
679                 File file = new File(path.replace('/', File.separatorChar));
680                 if (file.exists()) {
681                     intermediates.add(file);
682                 }
683             }
684         }
685 
686         return intermediates;
687     }
688 
extractAospMinSdkVersion()689     private void extractAospMinSdkVersion() {
690         // Is the SDK level specified by a Makefile?
691         boolean found = false;
692         File makefile = new File(mDir, "Android.mk"); //$NON-NLS-1$
693         if (makefile.exists()) {
694             try {
695                 List<String> lines = Files.readLines(makefile, Charsets.UTF_8);
696                 Pattern p = Pattern.compile("LOCAL_SDK_VERSION\\s*:=\\s*(.*)"); //$NON-NLS-1$
697                 for (String line : lines) {
698                     line = line.trim();
699                     Matcher matcher = p.matcher(line);
700                     if (matcher.matches()) {
701                         found = true;
702                         String version = matcher.group(1);
703                         if (version.equals("current")) { //$NON-NLS-1$
704                             mMinSdk = findCurrentAospVersion();
705                         } else {
706                             try {
707                                 mMinSdk = Integer.valueOf(version);
708                             } catch (NumberFormatException e) {
709                                 // Codename - just use current
710                                 mMinSdk = findCurrentAospVersion();
711                             }
712                         }
713                         break;
714                     }
715                 }
716             } catch (IOException ioe) {
717                 mClient.log(ioe, null);
718             }
719         }
720 
721         if (!found) {
722             mMinSdk = findCurrentAospVersion();
723         }
724     }
725 
726     /** Cache for {@link #findCurrentAospVersion()} */
727     private static int sCurrentVersion;
728 
729     /** In an AOSP build environment, identify the currently built image version, if available */
findCurrentAospVersion()730     private int findCurrentAospVersion() {
731         if (sCurrentVersion < 1) {
732             File apiDir = new File(getAospTop(), "frameworks/base/api" //$NON-NLS-1$
733                     .replace('/', File.separatorChar));
734             File[] apiFiles = apiDir.listFiles();
735             int max = 1;
736             for (File apiFile : apiFiles) {
737                 String name = apiFile.getName();
738                 int index = name.indexOf('.');
739                 if (index > 0) {
740                     String base = name.substring(0, index);
741                     if (Character.isDigit(base.charAt(0))) {
742                         try {
743                             int version = Integer.parseInt(base);
744                             if (version > max) {
745                                 max = version;
746                             }
747                         } catch (NumberFormatException nufe) {
748                             // pass
749                         }
750                     }
751                 }
752             }
753             sCurrentVersion = max;
754         }
755 
756         return sCurrentVersion;
757     }
758 }
759