PageRenderTime 26ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/projects/netbeans-7.3/nbbuild/antsrc/org/netbeans/nbbuild/LayerIndex.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 621 lines | 521 code | 29 blank | 71 comment | 154 complexity | 2b201bebe8639b1d1e6dee6f9c11c03e MD5 | raw file
  1. /*
  2. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3. *
  4. * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
  5. *
  6. * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  7. * Other names may be trademarks of their respective owners.
  8. *
  9. * The contents of this file are subject to the terms of either the GNU
  10. * General Public License Version 2 only ("GPL") or the Common
  11. * Development and Distribution License("CDDL") (collectively, the
  12. * "License"). You may not use this file except in compliance with the
  13. * License. You can obtain a copy of the License at
  14. * http://www.netbeans.org/cddl-gplv2.html
  15. * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
  16. * specific language governing permissions and limitations under the
  17. * License. When distributing the software, include this License Header
  18. * Notice in each file and include the License file at
  19. * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
  20. * particular file as subject to the "Classpath" exception as provided
  21. * by Oracle in the GPL Version 2 section of the License file that
  22. * accompanied this code. If applicable, add the following below the
  23. * License Header, with the fields enclosed by brackets [] replaced by
  24. * your own identifying information:
  25. * "Portions Copyrighted [year] [name of copyright owner]"
  26. *
  27. * Contributor(s):
  28. *
  29. * The Original Software is NetBeans. The Initial Developer of the Original
  30. * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
  31. * Microsystems, Inc. All Rights Reserved.
  32. *
  33. * If you wish your version of this file to be governed by only the CDDL
  34. * or only the GPL Version 2, indicate your decision by adding
  35. * "[Contributor] elects to include this software in this distribution
  36. * under the [CDDL or GPL Version 2] license." If you do not indicate a
  37. * single choice of license, a recipient has the option to distribute
  38. * your version of this file under either the CDDL, the GPL Version 2 or
  39. * to extend the choice of license to its licensees as provided above.
  40. * However, if you add GPL Version 2 code and therefore, elected the GPL
  41. * Version 2 license, then the option applies only if the new code is
  42. * made subject to such option by the copyright holder.
  43. */
  44. package org.netbeans.nbbuild;
  45. import java.io.BufferedReader;
  46. import java.io.File;
  47. import java.io.IOException;
  48. import java.io.InputStream;
  49. import java.io.InputStreamReader;
  50. import java.io.PrintWriter;
  51. import java.io.StringReader;
  52. import java.util.ArrayList;
  53. import java.util.Comparator;
  54. import java.util.Enumeration;
  55. import java.util.HashMap;
  56. import java.util.HashSet;
  57. import java.util.List;
  58. import java.util.Map;
  59. import java.util.Properties;
  60. import java.util.Set;
  61. import java.util.SortedMap;
  62. import java.util.SortedSet;
  63. import java.util.StringTokenizer;
  64. import java.util.TreeMap;
  65. import java.util.TreeSet;
  66. import java.util.jar.JarEntry;
  67. import java.util.jar.JarFile;
  68. import java.util.jar.Manifest;
  69. import java.util.regex.Matcher;
  70. import java.util.regex.Pattern;
  71. import java.util.zip.ZipEntry;
  72. import java.util.zip.ZipException;
  73. import java.util.zip.ZipFile;
  74. import javax.xml.parsers.SAXParserFactory;
  75. import org.apache.tools.ant.BuildException;
  76. import org.apache.tools.ant.DirectoryScanner;
  77. import org.apache.tools.ant.Project;
  78. import org.apache.tools.ant.Task;
  79. import org.apache.tools.ant.types.FileSet;
  80. import org.apache.tools.ant.types.ResourceCollection;
  81. import org.apache.tools.ant.types.resources.ZipResource;
  82. import org.xml.sax.Attributes;
  83. import org.xml.sax.InputSource;
  84. import org.xml.sax.SAXException;
  85. import org.xml.sax.helpers.DefaultHandler;
  86. /**
  87. * Task to scan all XML layers in a NB installation
  88. * and report on which modules registers which files.
  89. * @author Jesse Glick
  90. */
  91. public class LayerIndex extends Task {
  92. public LayerIndex() {}
  93. List<FileSet> filesets = new ArrayList<FileSet>();
  94. public void addConfiguredModules(FileSet fs) {
  95. filesets.add(fs);
  96. }
  97. private File output;
  98. public void setOutput(File f) {
  99. output = f;
  100. }
  101. private File serviceOutput;
  102. public void setServiceOutput(File f) {
  103. serviceOutput = f;
  104. }
  105. private String resourceId;
  106. private List<ZipResource> resources;
  107. /** If this parameter is provided, then this tasks creates a resource
  108. * composed from all the layerfiles and makes it accessible under this refId
  109. * @param id the refId to associate the collection with
  110. */
  111. public void setResourceId(String id) {
  112. resourceId = id;
  113. resources = new ZipArray();
  114. }
  115. @Override
  116. public void execute() throws BuildException {
  117. if (filesets.isEmpty()) {
  118. throw new BuildException();
  119. }
  120. SortedMap<String,String> files = new TreeMap<String,String>(); // layer path -> cnb
  121. SortedMap<String,SortedMap<String,String>> labels = new TreeMap<String,SortedMap<String,String>>(); // layer path -> cnb -> label
  122. final Map<String,Integer> positions = new TreeMap<String,Integer>(); // layer path -> position
  123. SortedMap<String,SortedMap<String,Set<String>>> serviceImpls = new TreeMap<String,SortedMap<String,Set<String>>>(); // path -> interface -> [impl]
  124. Map<String,Integer> servicePositions = new HashMap<String,Integer>(); // impl -> position
  125. for (FileSet fs : filesets) {
  126. DirectoryScanner ds = fs.getDirectoryScanner(getProject());
  127. File basedir = ds.getBasedir();
  128. for (String path : ds.getIncludedFiles()) {
  129. File jar = new File(basedir, path);
  130. try {
  131. JarFile jf = new JarFile(jar);
  132. try {
  133. Manifest mf = jf.getManifest();
  134. if (mf == null) {
  135. continue;
  136. }
  137. String modname = JarWithModuleAttributes.extractCodeName(mf.getMainAttributes());
  138. if (modname == null) {
  139. continue;
  140. }
  141. // XXX services.txt has e.g. "SERVICE org.openide.filesystems.MIMEResolver\n PROVIDER org.netbeans.modules.java.hints.test.Utilities$JavaMimeResolver"
  142. // which is misleading since this pseudomodule is used only in unit tests
  143. // maybe define Normally-Disabled: true in manifest.mf and skip from here (and disabledAutoloads)?
  144. String cnb = modname.replaceFirst("/\\d+$", "");
  145. String layer = mf.getMainAttributes().getValue("OpenIDE-Module-Layer");
  146. if (layer != null) {
  147. if (resources != null) {
  148. ZipResource res = new LayerResource(jar, layer, layer.replaceFirst("/[^/]+$", "").replace('/', '.') + ".xml");
  149. resources.add(res);
  150. } else {
  151. parse(jf.getInputStream(jf.getEntry(layer)), files, labels, positions, cnb, jf);
  152. }
  153. }
  154. ZipEntry generatedLayer = jf.getEntry("META-INF/generated-layer.xml");
  155. if (generatedLayer != null) {
  156. if (resources != null) {
  157. ZipResource res = new LayerResource(jar, generatedLayer.getName(), cnb + "-generated.xml");
  158. resources.add(res);
  159. } else {
  160. parse(jf.getInputStream(generatedLayer), files, labels, positions, cnb + "@", jf);
  161. }
  162. }
  163. if (serviceOutput != null) {
  164. // Could remember CNBs too.
  165. parseServices(jf, serviceImpls, servicePositions);
  166. }
  167. } finally {
  168. jf.close();
  169. }
  170. } catch (Exception x) {
  171. throw new BuildException("Reading " + jar + ": " + x, x, getLocation());
  172. }
  173. }
  174. }
  175. if (resources != null) {
  176. assignReferences();
  177. return;
  178. }
  179. try {
  180. writeLayerIndex(files, positions, labels);
  181. if (serviceOutput != null) {
  182. writeServiceIndex(serviceImpls, servicePositions);
  183. }
  184. } catch (IOException x) {
  185. throw new BuildException(x, getLocation());
  186. }
  187. }
  188. @SuppressWarnings("unchecked")
  189. private void assignReferences() {
  190. getProject().getReferences().put(resourceId, resources);
  191. }
  192. static String shortenCNB(String cnb) {
  193. if (cnb != null) {
  194. return cnb.replaceFirst("^org\\.netbeans\\.", "o.n.").replaceFirst("^org\\.openide\\.", "o.o.").replaceFirst("\\.modules\\.", ".m.");
  195. } else {
  196. return "";
  197. }
  198. }
  199. private String shortenPath(String path) {
  200. return path.replaceAll("(^|/)org-netbeans-", "$1o-n-").replaceAll("(^|/)org-openide-", "$1o-o-").replaceAll("-modules-", "-m-")
  201. .replaceAll("(^|/)org\\.netbeans\\.", "$1o.n.").replaceAll("(^|/)org\\.openide\\.", "$1o.o.").replaceAll("\\.modules\\.", ".m.");
  202. }
  203. private void parse(InputStream is, final Map<String,String> files, final SortedMap<String,SortedMap<String,String>> labels,
  204. final Map<String,Integer> positions, final String cnb, final JarFile jf) throws Exception {
  205. SAXParserFactory f = SAXParserFactory.newInstance();
  206. f.setValidating(false);
  207. f.setNamespaceAware(false);
  208. f.newSAXParser().parse(is, new DefaultHandler() {
  209. String prefix = "";
  210. void register(String path) {
  211. if (!files.containsKey(path)) {
  212. files.put(path, cnb);
  213. } else if (!cnb.equals(files.get(path))) {
  214. // Possibly >1 owner, but consider layer.xml vs. generated-layer.xml.
  215. if (cnb.equals(files.get(path) + "@")) {
  216. // leave alone
  217. } else if ((cnb + "@").equals(files.get(path))) { // mark as defined in layer.xml
  218. files.put(path, cnb);
  219. } else { // different modules
  220. files.put(path, null);
  221. }
  222. }
  223. }
  224. @Override
  225. public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
  226. if (qName.equals("folder")) {
  227. String n = attributes.getValue("name");
  228. prefix += n + "/";
  229. register(prefix);
  230. } else if (qName.equals("file")) {
  231. String n = attributes.getValue("name");
  232. prefix += n;
  233. register(prefix);
  234. } else if (qName.equals("attr") && attributes.getValue("name").equals("SystemFileSystem.localizingBundle")) {
  235. String bundle = attributes.getValue("stringvalue");
  236. if (bundle != null) {
  237. loadDisplayName(bundle,prefix.replaceAll("/$", ""));
  238. } else {
  239. log("No stringvalue for SystemFileSystem.localizingBundle on " + prefix + " in " + cnb, Project.MSG_WARN);
  240. }
  241. } else if (qName.equals("attr") && attributes.getValue("name").equals("displayName")) {
  242. String bundleKey = attributes.getValue("bundlevalue");
  243. if (bundleKey != null) {
  244. String[] bundlevalue = bundleKey.split("#", 2);
  245. loadDisplayName(bundlevalue[0], bundlevalue[1]);
  246. } else {
  247. String literal = attributes.getValue("stringvalue");
  248. if (literal != null) {
  249. loadDisplayName(literal);
  250. }
  251. }
  252. } else if (qName.equals("attr") && attributes.getValue("name").equals("position")) {
  253. String intvalue = attributes.getValue("intvalue");
  254. if (intvalue != null && /* #107550 */ !intvalue.equals("0")) {
  255. try {
  256. positions.put(prefix, Integer.parseInt(intvalue));
  257. } catch (NumberFormatException x) {
  258. throw new SAXException(x);
  259. }
  260. }
  261. }
  262. }
  263. private void loadDisplayName(String bundle, String key) throws SAXException {
  264. Properties props = new Properties();
  265. try {
  266. ZipEntry entry = jf.getEntry(bundle.replace('.', '/') + ".properties");
  267. if (entry == null) {
  268. /* Should be covered by ValidateLayerConsistencyTest.testLocalizingBundles:
  269. log(bundle + " not found in reference from " + prefix + " in " + cnb, Project.MSG_WARN);
  270. */
  271. return;
  272. }
  273. props.load(jf.getInputStream(entry));
  274. } catch (IOException x) {
  275. throw new SAXException(x);
  276. }
  277. String label = props.getProperty(key);
  278. if (label == null) {
  279. /* Should be covered by ValidateLayerConsistencyTest.testLocalizingBundles:
  280. log("Key " + key + " not found in " + bundle + " from " + cnb, Project.MSG_WARN);
  281. */
  282. return;
  283. }
  284. loadDisplayName(label);
  285. }
  286. private void loadDisplayName(String label) {
  287. SortedMap<String,String> cnb2label = labels.get(prefix);
  288. if (cnb2label == null) {
  289. cnb2label = new TreeMap<String,String>();
  290. labels.put(prefix, cnb2label);
  291. }
  292. cnb2label.put(cnb, label);
  293. }
  294. @Override
  295. public void endElement(String uri, String localName, String qName) throws SAXException {
  296. if (qName.equals("folder")) {
  297. prefix = prefix.replaceFirst("[^/]+/$", "");
  298. } else if (qName.equals("file")) {
  299. prefix = prefix.replaceFirst("[^/]+$", "");
  300. }
  301. }
  302. @Override
  303. public InputSource resolveEntity(String pub, String sys) throws IOException, SAXException {
  304. return new InputSource(new StringReader(""));
  305. }
  306. });
  307. }
  308. private void parseServices(JarFile jf, SortedMap<String,SortedMap<String,Set<String>>> serviceImplsByPath,
  309. Map<String,Integer> servicePositions) throws IOException {
  310. Enumeration<JarEntry> entries = jf.entries();
  311. while (entries.hasMoreElements()) {
  312. JarEntry entry = entries.nextElement();
  313. if (entry.isDirectory()) {
  314. continue;
  315. }
  316. String name = entry.getName();
  317. String path, xface;
  318. if (name.startsWith("META-INF/services/")) {
  319. path = "";
  320. xface = name.substring("META-INF/services/".length());
  321. } else if (name.startsWith("META-INF/namedservices/")) {
  322. String rest = name.substring("META-INF/namedservices/".length());
  323. int x = rest.lastIndexOf('/');
  324. path = rest.substring(0, x);
  325. xface = rest.substring(x + 1);
  326. } else {
  327. continue;
  328. }
  329. InputStream is = jf.getInputStream(entry);
  330. try {
  331. BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
  332. String lastImpl = null;
  333. String line;
  334. while ((line = r.readLine()) != null) {
  335. if (line.startsWith("#position=") && lastImpl != null) {
  336. servicePositions.put(lastImpl, Integer.parseInt(line.substring("#position=".length())));
  337. } else if (line.startsWith("#-") || (line.length() > 0 && !line.startsWith("#"))) {
  338. lastImpl = line;
  339. SortedMap<String,Set<String>> serviceImpls = serviceImplsByPath.get(path);
  340. if (serviceImpls == null) {
  341. serviceImpls = new TreeMap<String,Set<String>>();
  342. serviceImplsByPath.put(path, serviceImpls);
  343. }
  344. Set<String> impls = serviceImpls.get(xface);
  345. if (impls == null) {
  346. impls = new HashSet<String>();
  347. serviceImpls.put(xface, impls);
  348. }
  349. impls.add(lastImpl);
  350. }
  351. }
  352. } finally {
  353. is.close();
  354. }
  355. }
  356. }
  357. private static final class ZipArray extends ArrayList<ZipResource>
  358. implements ResourceCollection {
  359. public boolean isFilesystemOnly() {
  360. return false;
  361. }
  362. }
  363. private static final class LayerResource extends ZipResource {
  364. private final String name;
  365. private LayerResource(
  366. File jar, String path, String name) throws ZipException {
  367. super(jar, "UTF-8", new org.apache.tools.zip.ZipEntry(path));
  368. this.name = name;
  369. }
  370. @Override
  371. public String getName() {
  372. return name;
  373. }
  374. @Override
  375. public InputStream getInputStream() throws IOException {
  376. final ZipFile z = new ZipFile(getZipfile(), ZipFile.OPEN_READ);
  377. ZipEntry ze = z.getEntry(super.getName());
  378. if (ze == null) {
  379. z.close();
  380. throw new BuildException("no entry " + getName() + " in "
  381. + getArchive());
  382. }
  383. return z.getInputStream(ze);
  384. }
  385. }
  386. private void writeLayerIndex(SortedMap<String,String> files, final Map<String,Integer> positions,
  387. SortedMap<String,SortedMap<String,String>> labels) throws IOException {
  388. int maxlength = 0;
  389. for (String cnb : files.values()) {
  390. maxlength = Math.max(maxlength, shortenCNB(cnb).length());
  391. }
  392. PrintWriter pw = output != null ? new PrintWriter(output, "UTF-8") : null;
  393. Map<String,String> virtualEntries = computeMIMELookupEntries(files.keySet());
  394. updateMap(files, virtualEntries);
  395. updateMap(positions, virtualEntries);
  396. updateMap(labels, virtualEntries);
  397. SortedSet<String> layerPaths = new TreeSet<String>(new LayerPathComparator(positions));
  398. layerPaths.addAll(files.keySet());
  399. SortedSet<String> remaining = new TreeSet<String>(files.keySet());
  400. remaining.removeAll(layerPaths);
  401. assert remaining.isEmpty() : remaining;
  402. for (String path : layerPaths) {
  403. String cnb = files.get(path);
  404. String line = String.format("%-" + maxlength + "s %s", shortenCNB(cnb), shortenPath(path));
  405. if (virtualEntries.containsKey(path)) {
  406. line += " (merged)";
  407. }
  408. Integer pos = positions.get(path);
  409. if (pos != null) {
  410. line += String.format(" @%d", pos);
  411. }
  412. SortedMap<String,String> cnb2Label = labels.get(path);
  413. if (cnb2Label != null) {
  414. if (cnb2Label.size() == 1 && cnb2Label.keySet().iterator().next().equals(cnb)) {
  415. line += String.format(" (\"%s\")", cnb2Label.values().iterator().next());
  416. } else {
  417. for (Map.Entry<String,String> labelEntry : cnb2Label.entrySet()) {
  418. line += String.format(" (%s: \"%s\")", shortenCNB(labelEntry.getKey()), labelEntry.getValue());
  419. }
  420. }
  421. }
  422. if (pw != null) {
  423. pw.println(line);
  424. } else {
  425. log(line);
  426. }
  427. }
  428. if (pw != null) {
  429. pw.close();
  430. }
  431. if (output != null) {
  432. log(output + ": layer index written");
  433. }
  434. }
  435. /**
  436. * Map from virtual file paths to original literal file path.
  437. * E.g. Editors/text/html/Popup/foo.instance -> Editors/Popup/foo.instance
  438. * See ValidateLayerConsistencyTest.testFolderOrdering for comparison.
  439. */
  440. private Map<String,String> computeMIMELookupEntries(Set<String> files) {
  441. Pattern editorFolderPattern = Pattern.compile("Editors/(application|text)/([^/]+)(.*/)");
  442. Map<String,String> result = new HashMap<String,String>();
  443. for (String editorFolder : files) {
  444. Matcher m = editorFolderPattern.matcher(editorFolder);
  445. if (!m.matches()) {
  446. continue;
  447. }
  448. // $0="Editors/text/html/Popup/" $1="text" $2="html" $3="/Popup/"
  449. List<String> prefixen = new ArrayList<String>(2);
  450. prefixen.add("Editors" + m.group(3)); // "Editors/Popup/"
  451. if (m.group(2).endsWith("+xml")) { // Editors/text/x-ant+xml/Popup/
  452. prefixen.add("Editors/" + m.group(1) + "/xml" + m.group(3)); // Editors/text/xml/Popup/
  453. }
  454. for (String prefix : prefixen) {
  455. for (String file : files) {
  456. if (file.startsWith(prefix)) { // "Editors/Popup/foo.instance"
  457. String basename = file.substring(prefix.length());
  458. if (basename.contains("/")) {
  459. // Would technically be correct to show, but usually irrelevant.
  460. continue;
  461. }
  462. String virtual = editorFolder + basename; // Editors/text/html/Popup/foo.instance
  463. if (!files.contains(virtual)) {
  464. result.put(virtual, file);
  465. }
  466. }
  467. }
  468. }
  469. }
  470. return result;
  471. }
  472. private <T> void updateMap(Map<String,T> map, Map<String,String> virtualEntries) {
  473. for (Map.Entry<String,String> entry : virtualEntries.entrySet()) {
  474. String orig = entry.getValue();
  475. if (map.containsKey(orig)) {
  476. map.put(entry.getKey(), map.get(orig));
  477. }
  478. }
  479. }
  480. private static class LayerPathComparator implements Comparator<String> {
  481. private final Map<String,Integer> positions;
  482. public LayerPathComparator(Map<String,Integer> positions) {
  483. this.positions = positions;
  484. }
  485. public int compare(String p1, String p2) {
  486. StringTokenizer tok1 = new StringTokenizer(p1, "/");
  487. StringTokenizer tok2 = new StringTokenizer(p2, "/");
  488. String prefix = "";
  489. while (tok1.hasMoreTokens()) {
  490. String piece1 = tok1.nextToken();
  491. if (tok2.hasMoreTokens()) {
  492. String piece2 = tok2.nextToken();
  493. if (piece1.equals(piece2)) {
  494. prefix += piece1 + "/";
  495. } else {
  496. Integer pos1 = pos(prefix + piece1);
  497. Integer pos2 = pos(prefix + piece2);
  498. if (pos1 == null) {
  499. if (pos2 == null) {
  500. return piece1.compareTo(piece2);
  501. } else {
  502. return 1;
  503. }
  504. } else {
  505. if (pos2 == null) {
  506. return -1;
  507. } else {
  508. int diff = pos1 - pos2;
  509. if (diff != 0) {
  510. return diff;
  511. } else {
  512. return piece1.compareTo(piece2);
  513. }
  514. }
  515. }
  516. }
  517. } else {
  518. return 1;
  519. }
  520. }
  521. if (tok2.hasMoreTokens()) {
  522. return -1;
  523. }
  524. assert p1.equals(p2) : p1 + " vs. " + p2;
  525. return 0;
  526. }
  527. Integer pos(String path) {
  528. return positions.containsKey(path) ? positions.get(path) : positions.get(path + "/");
  529. }
  530. }
  531. private void writeServiceIndex(SortedMap<String,SortedMap<String,Set<String>>> serviceImpls,
  532. final Map<String,Integer> servicePositions) throws IOException {
  533. PrintWriter pw = new PrintWriter(serviceOutput, "UTF-8");
  534. for (Map.Entry<String,SortedMap<String,Set<String>>> mainEntry : serviceImpls.entrySet()) {
  535. String path = mainEntry.getKey();
  536. for (Map.Entry<String,Set<String>> entry : mainEntry.getValue().entrySet()) {
  537. pw.print("SERVICE " + entry.getKey());
  538. if (path.length() > 0) {
  539. pw.println(" under " + path);
  540. } else {
  541. pw.println();
  542. }
  543. SortedSet<String> impls = new TreeSet<String>(new ServiceComparator(servicePositions));
  544. impls.addAll(entry.getValue());
  545. Set<String> masked = new HashSet<String>();
  546. for (String impl : impls) {
  547. if (impl.startsWith("#-")) {
  548. masked.add(impl);
  549. masked.add(impl.substring(2));
  550. }
  551. }
  552. impls.removeAll(masked);
  553. for (String impl : impls) {
  554. if (servicePositions.containsKey(impl)) {
  555. impl += " @" + servicePositions.get(impl);
  556. }
  557. pw.println(" PROVIDER " + impl);
  558. }
  559. }
  560. }
  561. pw.close();
  562. log(serviceOutput + ": service index written");
  563. }
  564. private static class ServiceComparator implements Comparator<String> {
  565. private final Map<String,Integer> servicePositions;
  566. public ServiceComparator(Map<String,Integer> servicePositions) {
  567. this.servicePositions = servicePositions;
  568. }
  569. public int compare(String i1, String i2) {
  570. Integer pos1 = servicePositions.get(i1);
  571. Integer pos2 = servicePositions.get(i2);
  572. if (pos1 == null) {
  573. if (pos2 == null) {
  574. return i1.compareTo(i2);
  575. } else {
  576. return 1;
  577. }
  578. } else {
  579. if (pos2 == null) {
  580. return -1;
  581. } else {
  582. int diff = pos1 - pos2;
  583. if (diff != 0) {
  584. return diff;
  585. } else {
  586. return i1.compareTo(i2);
  587. }
  588. }
  589. }
  590. }
  591. }
  592. }