OSDN Git Service

am e7ad6a5e: Merge change I2fb42327 into eclair
[android-x86/sdk.git] / sdkmanager / libs / sdkuilib / src / com / android / sdkuilib / internal / repository / UpdaterLogic.java
1 /*\r
2  * Copyright (C) 2009 The Android Open Source Project\r
3  *\r
4  * Licensed under the Apache License, Version 2.0 (the "License");\r
5  * you may not use this file except in compliance with the License.\r
6  * You may obtain a copy of the License at\r
7  *\r
8  *      http://www.apache.org/licenses/LICENSE-2.0\r
9  *\r
10  * Unless required by applicable law or agreed to in writing, software\r
11  * distributed under the License is distributed on an "AS IS" BASIS,\r
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
13  * See the License for the specific language governing permissions and\r
14  * limitations under the License.\r
15  */\r
16 \r
17 package com.android.sdkuilib.internal.repository;\r
18 \r
19 import com.android.sdklib.AndroidVersion;\r
20 import com.android.sdklib.internal.repository.AddonPackage;\r
21 import com.android.sdklib.internal.repository.Archive;\r
22 import com.android.sdklib.internal.repository.MinToolsPackage;\r
23 import com.android.sdklib.internal.repository.Package;\r
24 import com.android.sdklib.internal.repository.PlatformPackage;\r
25 import com.android.sdklib.internal.repository.RepoSource;\r
26 import com.android.sdklib.internal.repository.RepoSources;\r
27 import com.android.sdklib.internal.repository.ToolPackage;\r
28 import com.android.sdklib.internal.repository.Package.UpdateInfo;\r
29 \r
30 import java.util.ArrayList;\r
31 import java.util.Collection;\r
32 \r
33 /**\r
34  * The logic to compute which packages to install, based on the choices\r
35  * made by the user. This adds dependent packages as needed.\r
36  * <p/>\r
37  * When the user doesn't provide a selection, looks at local package to find\r
38  * those that can be updated and compute dependencies too.\r
39  */\r
40 class UpdaterLogic {\r
41 \r
42     private RepoSources mSources;\r
43 \r
44     /**\r
45      * Compute which packages to install by taking the user selection\r
46      * and adding dependent packages as needed.\r
47      *\r
48      * When the user doesn't provide a selection, looks at local package to find\r
49      * those that can be updated and compute dependencies too.\r
50      */\r
51     public ArrayList<ArchiveInfo> computeUpdates(\r
52             Collection<Archive> selectedArchives,\r
53             RepoSources sources,\r
54             Package[] localPkgs) {\r
55 \r
56         mSources = sources;\r
57         ArrayList<ArchiveInfo> archives = new ArrayList<ArchiveInfo>();\r
58         ArrayList<Package> remotePkgs = new ArrayList<Package>();\r
59 \r
60         if (selectedArchives == null) {\r
61             selectedArchives = findUpdates(localPkgs, remotePkgs);\r
62         }\r
63 \r
64         for (Archive a : selectedArchives) {\r
65             insertArchive(a, archives, selectedArchives, remotePkgs, localPkgs, false);\r
66         }\r
67 \r
68         return archives;\r
69     }\r
70 \r
71 \r
72     /**\r
73      * Find suitable updates to all current local packages.\r
74      */\r
75     private Collection<Archive> findUpdates(Package[] localPkgs, ArrayList<Package> remotePkgs) {\r
76         ArrayList<Archive> updates = new ArrayList<Archive>();\r
77 \r
78         fetchRemotePackages(remotePkgs);\r
79 \r
80         for (Package localPkg : localPkgs) {\r
81             for (Package remotePkg : remotePkgs) {\r
82                 if (localPkg.canBeUpdatedBy(remotePkg) == UpdateInfo.UPDATE) {\r
83                     // Found a suitable update. Only accept the remote package\r
84                     // if it provides at least one compatible archive.\r
85 \r
86                     for (Archive a : remotePkg.getArchives()) {\r
87                         if (a.isCompatible()) {\r
88                             updates.add(a);\r
89                             break;\r
90                         }\r
91                     }\r
92                 }\r
93             }\r
94         }\r
95 \r
96         return updates;\r
97     }\r
98 \r
99     private ArchiveInfo insertArchive(Archive archive,\r
100             ArrayList<ArchiveInfo> outArchives,\r
101             Collection<Archive> selectedArchives,\r
102             ArrayList<Package> remotePkgs,\r
103             Package[] localPkgs,\r
104             boolean automated) {\r
105         Package p = archive.getParentPackage();\r
106 \r
107         // Is this an update?\r
108         Archive updatedArchive = null;\r
109         for (Package lp : localPkgs) {\r
110             assert lp.getArchives().length == 1;\r
111             if (lp.getArchives().length > 0 && lp.canBeUpdatedBy(p) == UpdateInfo.UPDATE) {\r
112                 updatedArchive = lp.getArchives()[0];\r
113             }\r
114         }\r
115 \r
116         // find dependencies\r
117         ArchiveInfo dep = findDependency(p, outArchives, selectedArchives, remotePkgs, localPkgs);\r
118 \r
119         ArchiveInfo ai = new ArchiveInfo(\r
120                 archive, //newArchive\r
121                 updatedArchive, //replaced\r
122                 dep //dependsOn\r
123                 );\r
124 \r
125         outArchives.add(ai);\r
126         if (dep != null) {\r
127             dep.addDependencyFor(ai);\r
128         }\r
129 \r
130         return ai;\r
131     }\r
132 \r
133     private ArchiveInfo findDependency(Package pkg,\r
134             ArrayList<ArchiveInfo> outArchives,\r
135             Collection<Archive> selectedArchives,\r
136             ArrayList<Package> remotePkgs,\r
137             Package[] localPkgs) {\r
138 \r
139         // Current dependencies can be:\r
140         // - addon: *always* depends on platform of same API level\r
141         // - platform: *might* depends on tools of rev >= min-tools-rev\r
142 \r
143         if (pkg instanceof AddonPackage) {\r
144             AddonPackage addon = (AddonPackage) pkg;\r
145 \r
146             return findPlatformDependency(\r
147                     addon, outArchives, selectedArchives, remotePkgs, localPkgs);\r
148 \r
149         } else if (pkg instanceof MinToolsPackage) {\r
150             MinToolsPackage platformOrExtra = (MinToolsPackage) pkg;\r
151 \r
152             return findToolsDependency(\r
153                     platformOrExtra, outArchives, selectedArchives, remotePkgs, localPkgs);\r
154         }\r
155 \r
156         return null;\r
157     }\r
158 \r
159     /**\r
160      * Resolves dependencies on tools.\r
161      *\r
162      * A platform or an extra package can both have a min-tools-rev, in which case it\r
163      * depends on having a tools package of the requested revision.\r
164      * Finds the tools dependency. If found, add it to the list of things to install.\r
165      * Returns the archive info dependency, if any.\r
166      */\r
167     protected ArchiveInfo findToolsDependency(MinToolsPackage platformOrExtra,\r
168             ArrayList<ArchiveInfo> outArchives,\r
169             Collection<Archive> selectedArchives,\r
170             ArrayList<Package> remotePkgs,\r
171             Package[] localPkgs) {\r
172         // This is the requirement to match.\r
173         int rev = platformOrExtra.getMinToolsRevision();\r
174 \r
175         if (rev == MinToolsPackage.MIN_TOOLS_REV_NOT_SPECIFIED) {\r
176             // Well actually there's no requirement.\r
177             return null;\r
178         }\r
179 \r
180         // First look in local packages.\r
181         for (Package p : localPkgs) {\r
182             if (p instanceof ToolPackage) {\r
183                 if (((ToolPackage) p).getRevision() >= rev) {\r
184                     // We found one already installed. We don't report this dependency\r
185                     // as the UI only cares about resolving "newly added dependencies".\r
186                     return null;\r
187                 }\r
188             }\r
189         }\r
190 \r
191         // Look in archives already scheduled for install\r
192         for (ArchiveInfo ai : outArchives) {\r
193             Package p = ai.getNewArchive().getParentPackage();\r
194             if (p instanceof PlatformPackage) {\r
195                 if (((ToolPackage) p).getRevision() >= rev) {\r
196                     // The dependency is already scheduled for install, nothing else to do.\r
197                     return ai;\r
198                 }\r
199             }\r
200         }\r
201 \r
202         // Otherwise look in the selected archives.\r
203         for (Archive a : selectedArchives) {\r
204             Package p = a.getParentPackage();\r
205             if (p instanceof ToolPackage) {\r
206                 if (((ToolPackage) p).getRevision() >= rev) {\r
207                     // It's not already in the list of things to install, so add it now\r
208                     return insertArchive(a, outArchives,\r
209                             selectedArchives, remotePkgs, localPkgs,\r
210                             true);\r
211                 }\r
212             }\r
213         }\r
214 \r
215         // Finally nothing matched, so let's look at all available remote packages\r
216         fetchRemotePackages(remotePkgs);\r
217         for (Package p : remotePkgs) {\r
218             if (p instanceof ToolPackage) {\r
219                 if (((ToolPackage) p).getRevision() >= rev) {\r
220                     // It's not already in the list of things to install, so add the\r
221                     // first compatible archive we can find.\r
222                     for (Archive a : p.getArchives()) {\r
223                         if (a.isCompatible()) {\r
224                             return insertArchive(a, outArchives,\r
225                                     selectedArchives, remotePkgs, localPkgs,\r
226                                     true);\r
227                         }\r
228                     }\r
229                 }\r
230             }\r
231         }\r
232 \r
233         // We end up here if nothing matches. We don't have a good tools to match.\r
234         // Seriously, that can't happens unless we totally screwed our repo manifest.\r
235         // We'll let this one go through anyway.\r
236         return null;\r
237     }\r
238 \r
239     /**\r
240      * Resolves dependencies on platform.\r
241      *\r
242      * An addon depends on having a platform with the same API version.\r
243      * Finds the platform dependency. If found, add it to the list of things to install.\r
244      * Returns the archive info dependency, if any.\r
245      */\r
246     protected ArchiveInfo findPlatformDependency(AddonPackage addon,\r
247             ArrayList<ArchiveInfo> outArchives,\r
248             Collection<Archive> selectedArchives,\r
249             ArrayList<Package> remotePkgs,\r
250             Package[] localPkgs) {\r
251         // This is the requirement to match.\r
252         AndroidVersion v = addon.getVersion();\r
253 \r
254         // Find a platform that would satisfy the requirement.\r
255 \r
256         // First look in local packages.\r
257         for (Package p : localPkgs) {\r
258             if (p instanceof PlatformPackage) {\r
259                 if (v.equals(((PlatformPackage) p).getVersion())) {\r
260                     // We found one already installed. We don't report this dependency\r
261                     // as the UI only cares about resolving "newly added dependencies".\r
262                     return null;\r
263                 }\r
264             }\r
265         }\r
266 \r
267         // Look in archives already scheduled for install\r
268         for (ArchiveInfo ai : outArchives) {\r
269             Package p = ai.getNewArchive().getParentPackage();\r
270             if (p instanceof PlatformPackage) {\r
271                 if (v.equals(((PlatformPackage) p).getVersion())) {\r
272                     // The dependency is already scheduled for install, nothing else to do.\r
273                     return ai;\r
274                 }\r
275             }\r
276         }\r
277 \r
278         // Otherwise look in the selected archives.\r
279         for (Archive a : selectedArchives) {\r
280             Package p = a.getParentPackage();\r
281             if (p instanceof PlatformPackage) {\r
282                 if (v.equals(((PlatformPackage) p).getVersion())) {\r
283                     // It's not already in the list of things to install, so add it now\r
284                     return insertArchive(a, outArchives,\r
285                             selectedArchives, remotePkgs, localPkgs,\r
286                             true);\r
287                 }\r
288             }\r
289         }\r
290 \r
291         // Finally nothing matched, so let's look at all available remote packages\r
292         fetchRemotePackages(remotePkgs);\r
293         for (Package p : remotePkgs) {\r
294             if (p instanceof PlatformPackage) {\r
295                 if (v.equals(((PlatformPackage) p).getVersion())) {\r
296                     // It's not already in the list of things to install, so add the\r
297                     // first compatible archive we can find.\r
298                     for (Archive a : p.getArchives()) {\r
299                         if (a.isCompatible()) {\r
300                             return insertArchive(a, outArchives,\r
301                                     selectedArchives, remotePkgs, localPkgs,\r
302                                     true);\r
303                         }\r
304                     }\r
305                 }\r
306             }\r
307         }\r
308 \r
309         // We end up here if nothing matches. We don't have a good platform to match.\r
310         // Seriously, that can't happens unless the repository contains a bogus addon\r
311         // entry that does not match any existing platform API level.\r
312         // It's conceivable that a 3rd part addon repo might have error, in which case\r
313         // we'll let this one go through anyway.\r
314         return null;\r
315     }\r
316 \r
317     /** Fetch all remote packages only if really needed. */\r
318     protected void fetchRemotePackages(ArrayList<Package> remotePkgs) {\r
319         if (remotePkgs.size() > 0) {\r
320             return;\r
321         }\r
322 \r
323         // Get all the available packages from all loaded sources\r
324         RepoSource[] remoteSources = mSources.getSources();\r
325 \r
326         for (RepoSource remoteSrc : remoteSources) {\r
327             Package[] pkgs = remoteSrc.getPackages();\r
328             if (pkgs != null) {\r
329                 nextPackage: for (Package pkg : pkgs) {\r
330                     for (Archive a : pkg.getArchives()) {\r
331                         // Only add a package if it contains at least one compatible archive\r
332                         if (a.isCompatible()) {\r
333                             remotePkgs.add(pkg);\r
334                             continue nextPackage;\r
335                         }\r
336                     }\r
337                 }\r
338             }\r
339         }\r
340     }\r
341 \r
342 }\r