diff -r 7d779ce00a82 o.n.bootstrap/arch.xml --- a/o.n.bootstrap/arch.xml Mon Apr 15 14:03:49 2013 +0200 +++ b/o.n.bootstrap/arch.xml Mon Apr 15 16:47:24 2013 +0200 @@ -1110,6 +1110,23 @@ purposes of installer.

+ +

+ Lists relative paths of clusters (those defined by + netbeans.home and + netbeans.dirs) + for which the cache has been created. This is an important file + in the + shared cache as + it helps to ensure the set of clusters has not changed since + the cache has been generated. +

+

+ The file is binary and its format is private. The location + is however well known for + purposes of installer. +

+

Additional information about modules (like deprecation message, etc.) diff -r 7d779ce00a82 o.n.bootstrap/src/org/netbeans/Clusters.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/o.n.bootstrap/src/org/netbeans/Clusters.java Mon Apr 15 16:47:24 2013 +0200 @@ -0,0 +1,165 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2013 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2013 Sun Microsystems, Inc. + */ + +package org.netbeans; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +/** + * + * @author Jaroslav Tulach + */ +final class Clusters implements Stamps.Updater { + private static String[] dirs; + private static String dirPrefix; + private static final Clusters INSTANCE = new Clusters(); + + private Clusters() { + } + + static void scheduleSave(Stamps s) { + s.scheduleSaveImpl(INSTANCE, "all-clusters.dat", false); // NOI18N + } + + static boolean compareDirs(DataInputStream is) throws IOException { + int cnt = is.readInt(); + String[] arr = relativeDirsWithHome(); + if (cnt != arr.length) { + return false; + } + for (int i = 0; i < arr.length; i++) { + String cluster = is.readUTF(); + if (!cluster.equals(arr[i])) { + return false; + } + } + return true; + } + + static synchronized String[] dirs() { + if (dirs == null) { + List tmp = new ArrayList(); + String nbdirs = System.getProperty("netbeans.dirs"); + if (nbdirs != null) { + StringTokenizer tok = new StringTokenizer(nbdirs, File.pathSeparator); + while (tok.hasMoreTokens()) { + tmp.add(tok.nextToken()); + } + } + dirs = tmp.toArray(new String[tmp.size()]); + } + return dirs; + } + + static int findCommonPrefix(String s1, String s2) { + int len = Math.min(s1.length(), s2.length()); + int max = 0; + for (int i = 0; i < len; i++) { + final char ch = s1.charAt(i); + if (ch != s2.charAt(i)) { + return max; + } + if (ch == '/' || ch == File.separatorChar) { + max = i + 1; + } + } + return len; + } + + static synchronized String dirPrefix() { + if (dirPrefix == null) { + String p = System.getProperty("netbeans.home"); + for (String d : dirs()) { + if (p == null) { + p = d; + } else { + int len = findCommonPrefix(p, d); + if (len <= 3) { + p = ""; + break; + } + p = p.substring(0, len); + } + } + dirPrefix = p == null ? "" : p; + } + return dirPrefix; + } + + static String[] relativeDirsWithHome() { + String[] arr = dirs(); + String[] tmp = new String[arr.length + 1]; + tmp[0] = System.getProperty("netbeans.home", ""); // NOI18N + if (tmp[0].length() >= dirPrefix().length()) { + tmp[0] = tmp[0].substring(dirPrefix().length()); + } + for (int i = 0; i < arr.length; i++) { + tmp[i + 1] = arr[i].substring(dirPrefix().length()).replace(File.separatorChar, '/'); + } + return tmp; + } + + static synchronized void clear() { + dirs = null; + dirPrefix = null; + } + + @Override + public void flushCaches(DataOutputStream os) throws IOException { + String[] arr = relativeDirsWithHome(); + os.writeInt(arr.length); + for (int i = 0; i < arr.length; i++) { + os.writeUTF(arr[i]); + } + } + + @Override + public void cacheReady() { + } + +} diff -r 7d779ce00a82 o.n.bootstrap/src/org/netbeans/Stamps.java --- a/o.n.bootstrap/src/org/netbeans/Stamps.java Mon Apr 15 14:03:49 2013 +0200 +++ b/o.n.bootstrap/src/org/netbeans/Stamps.java Mon Apr 15 16:47:24 2013 +0200 @@ -45,6 +45,7 @@ import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.DataInput; +import java.io.DataInputStream; import java.io.DataOutput; import java.io.DataOutputStream; import java.io.File; @@ -68,7 +69,6 @@ import java.util.Locale; import java.util.Random; import java.util.Set; -import java.util.StringTokenizer; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -91,9 +91,9 @@ private static final Logger LOG = Logger.getLogger(Stamps.class.getName()); private static AtomicLong moduleJARs; private static File moduleNewestFile; - private static String[] dirs; private static File[] fallbackCache; private static boolean populated; + private static Boolean clustersChanged; private Worker worker = new Worker(); @@ -107,21 +107,24 @@ static void main(String... args) { if (args.length == 1 && "reset".equals(args[0])) { // NOI18N moduleJARs = null; - dirs = null; + Clusters.clear(); + clustersChanged = null; fallbackCache = null; stamp(false); return; } if (args.length == 1 && "init".equals(args[0])) { // NOI18N moduleJARs = null; - dirs = null; + Clusters.clear(); + clustersChanged = null; fallbackCache = null; stamp(true); return; } if (args.length == 1 && "clear".equals(args[0])) { // NOI18N moduleJARs = null; - dirs = null; + Clusters.clear(); + clustersChanged = null; fallbackCache = null; return; } @@ -183,6 +186,10 @@ return asByteBuffer(cache, true, false); } final File file(String cache, int[] len) { + if (clustersChanged()) { + return null; + } + checkPopulateCache(); synchronized (this) { @@ -191,34 +198,7 @@ return null; } } - - File cacheFile = Places.getCacheSubfile(cache); - long last = cacheFile.lastModified(); - if (last <= 0) { - LOG.log(Level.FINE, "Cache does not exist when asking for {0}", cache); // NOI18N - cacheFile = findFallbackCache(cache); - if (cacheFile == null || (last = cacheFile.lastModified()) <= 0) { - return null; - } - LOG.log(Level.FINE, "Found fallback cache at {0}", cacheFile); - } - - if (moduleJARs() > last) { - LOG.log(Level.FINE, "Timestamp does not pass when asking for {0}. Newest file {1}", new Object[] { cache, moduleNewestFile }); // NOI18N - return null; - } - - long longLen = cacheFile.length(); - if (longLen > Integer.MAX_VALUE) { - LOG.warning("Cache file is too big: " + longLen + " bytes for " + cacheFile); // NOI18N - return null; - } - if (len != null) { - len[0] = (int)longLen; - } - - LOG.log(Level.FINE, "Cache found: {0}", cache); // NOI18N - return cacheFile; + return fileImpl(cache, len, moduleJARs()); } private ByteBuffer asByteBuffer(String cache, boolean direct, boolean mmap) { @@ -260,12 +240,17 @@ */ public void scheduleSave(Updater updater, String cache, boolean append) { boolean firstAdd; - synchronized (worker) { - firstAdd = worker.addStorage(new Store(updater, cache, append)); - } + firstAdd = scheduleSaveImpl(updater, cache, append); LOG.log(firstAdd ? Level.FINE : Level.FINER, "Scheduling save for {0} cache", cache ); + Clusters.scheduleSave(this); + } + + final boolean scheduleSaveImpl(Updater updater, String cache, boolean append) { + synchronized (worker) { + return worker.addStorage(new Store(updater, cache, append)); + } } /** Flushes all caches. @@ -345,36 +330,23 @@ stamp(checkStampFile, result, newestFile); return result; } - - private static synchronized String[] dirs() { - if (dirs == null) { - List tmp = new ArrayList(); - String nbdirs = System.getProperty("netbeans.dirs"); // NOI18N - if (nbdirs != null) { - StringTokenizer tok = new StringTokenizer(nbdirs, File.pathSeparator); - while (tok.hasMoreTokens()) { - tmp.add(tok.nextToken()); - } - } - dirs = tmp.toArray(new String[tmp.size()]); - } - return dirs; - } private static void stamp(boolean checkStampFile, AtomicLong result, AtomicReference newestFile) { StringBuilder sb = new StringBuilder(); Set processedDirs = new HashSet(); + String[] relativeDirs = Clusters.relativeDirsWithHome(); String home = System.getProperty ("netbeans.home"); // NOI18N if (home != null) { long stamp = stampForCluster (new File (home), result, newestFile, processedDirs, checkStampFile, true, null); - sb.append("home=").append(stamp).append('\n'); + sb.append(relativeDirs[0]).append('=').append(stamp).append('\n'); } - for (String t : dirs()) { - final File clusterDir = new File(t); + String[] drs = Clusters.dirs(); + for (int i = 0; i < drs.length; i++) { + final File clusterDir = new File(drs[i]); long stamp = stampForCluster(clusterDir, result, newestFile, processedDirs, checkStampFile, true, null); if (stamp != -1) { - sb.append(clusterDir.getName()).append('=').append(stamp).append('\n'); + sb.append("cluster.").append(relativeDirs[i + 1]).append('=').append(stamp).append('\n'); } } File user = Places.getUserDirectory(); @@ -561,8 +533,8 @@ private static File findFallbackCache(String cache) { if (fallbackCache == null) { fallbackCache = new File[0]; - if (dirs().length >= 1) { - File fallback = new File(new File(new File(dirs()[0]), "var"), "cache"); // NOI18N + if (Clusters.dirs().length >= 1) { + File fallback = new File(new File(new File(Clusters.dirs()[0]), "var"), "cache"); // NOI18N if (fallback.isDirectory()) { fallbackCache = new File[]{ fallback }; } @@ -620,6 +592,69 @@ LOG.log(Level.INFO, "Failed to populate {0}", cache); } } + + private static boolean clustersChanged() { + if (clustersChanged != null) { + return clustersChanged; + } + + final String clustersCache = "all-clusters.dat"; // NOI18N + File f = fileImpl(clustersCache, null, -1); // no timestamp check + if (f != null) { + DataInputStream dis = null; + try { + dis = new DataInputStream(new FileInputStream(f)); + if (Clusters.compareDirs(dis)) { + return false; + } + } catch (IOException ex) { + return clustersChanged = true; + } finally { + if (dis != null) { + try { + dis.close(); + } catch (IOException ex) { + LOG.log(Level.INFO, null, ex); + } + } + } + } else { + // missing cluster file signals caches are OK, for + // backward compatibility + return clustersChanged = false; + } + return clustersChanged = true; + } + + private static File fileImpl(String cache, int[] len, long moduleJARs) { + File cacheFile = Places.getCacheSubfile(cache); + long last = cacheFile.lastModified(); + if (last <= 0) { + LOG.log(Level.FINE, "Cache does not exist when asking for {0}", cache); // NOI18N + cacheFile = findFallbackCache(cache); + if (cacheFile == null || (last = cacheFile.lastModified()) <= 0) { + return null; + } + LOG.log(Level.FINE, "Found fallback cache at {0}", cacheFile); + } + + if (moduleJARs > last) { + LOG.log(Level.FINE, "Timestamp does not pass when asking for {0}. Newest file {1}", new Object[] { cache, moduleNewestFile }); // NOI18N + return null; + } + + long longLen = cacheFile.length(); + if (longLen > Integer.MAX_VALUE) { + LOG.log(Level.WARNING, "Cache file is too big: {0} bytes for {1}", new Object[]{longLen, cacheFile}); // NOI18N + return null; + } + if (len != null) { + len[0] = (int)longLen; + } + + LOG.log(Level.FINE, "Cache found: {0}", cache); // NOI18N + return cacheFile; + } /** A callback interface to flush content of some cache at a suitable * point in time. @@ -908,7 +943,7 @@ return relative; } int indx = Integer.parseInt(index); - String[] _dirs = dirs(); + String[] _dirs = Clusters.dirs(); if (indx < 0 || indx >= _dirs.length) { throw new IOException("Bad index " + indx + " for " + Arrays.toString(_dirs)); } @@ -931,7 +966,7 @@ return; } int cnt = 0; - for (String p : dirs()) { + for (String p : Clusters.dirs()) { if (testWritePath(path, p, "" + cnt, out)) { return; } diff -r 7d779ce00a82 o.n.bootstrap/test/unit/src/org/netbeans/ModuleManagerPersistanceTest.java --- a/o.n.bootstrap/test/unit/src/org/netbeans/ModuleManagerPersistanceTest.java Mon Apr 15 14:03:49 2013 +0200 +++ b/o.n.bootstrap/test/unit/src/org/netbeans/ModuleManagerPersistanceTest.java Mon Apr 15 16:47:24 2013 +0200 @@ -72,7 +72,9 @@ clearWorkDir(); File home = new File(getWorkDir(), "home"); - new File(new File(home, "config"), "Modules").mkdirs(); + final File configModules = new File(new File(home, "config"), "Modules"); + configModules.mkdirs(); + new File(configModules, "a-b-c.xml").createNewFile(); File moduleDir = new File(home, "modules"); moduleDir.mkdirs(); System.setProperty("netbeans.home", home.getPath()); diff -r 7d779ce00a82 o.n.bootstrap/test/unit/src/org/netbeans/StampsClusterMovedTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/o.n.bootstrap/test/unit/src/org/netbeans/StampsClusterMovedTest.java Mon Apr 15 16:47:24 2013 +0200 @@ -0,0 +1,187 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2008 Sun Microsystems, Inc. + */ + +package org.netbeans; + +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import org.netbeans.junit.NbTestCase; + +/** + * + * @author Jaroslav Tulach + */ +public class StampsClusterMovedTest extends NbTestCase implements Stamps.Updater{ + private File userdir; + private File ide; + private File platform; + private File install; + private File mainCluster; + + public StampsClusterMovedTest(String testName) { + super(testName); + } + + public void testMoveOfAClusterIsDetected() throws Exception { + clearWorkDir(); + + install = new File(getWorkDir(), "install"); + platform = new File(install, "platform"); + platform.mkdirs(); + new File(platform, ".lastModified").createNewFile(); + ide = new File(install, "ide"); + ide.mkdirs(); + new File(ide, ".lastModified").createNewFile(); + mainCluster = new File(install, "extra"); + mainCluster.mkdirs(); + assertTrue("Extra cluster exists", mainCluster.isDirectory()); + new File(mainCluster, ".lastModified").createNewFile(); + userdir = new File(getWorkDir(), "tmp"); + + System.setProperty("netbeans.home", platform.getPath()); + System.setProperty("netbeans.dirs", ide.getPath() + File.pathSeparator + mainCluster.getPath()); + System.setProperty("netbeans.user", userdir.getPath()); + + + Thread.sleep(500); + long between = System.currentTimeMillis(); + Thread.sleep(500); + + + Stamps.main("init"); + + Stamps.getModulesJARs().scheduleSave(this, "test-cache", false); + Stamps.getModulesJARs().waitFor(true); + int[] arr = { 0 }; + File f = Stamps.getModulesJARs().file("test-cache", arr); + assertNotNull("Cache found", f); + assertEquals("Stamps of caches shall be the same as stamps of .lastModified", + f.lastModified(), Stamps.moduleJARs() + ); + + + Thread.sleep(500); + + File subDir = new File(getWorkDir(), "subdir"); + subDir.mkdirs(); + final File newExtra = new File(subDir, mainCluster.getName()); + boolean renRes = mainCluster.renameTo(newExtra); + assertTrue("Rename succeeded", renRes); + assertTrue("Extra renamed: " + newExtra, newExtra.isDirectory()); + + System.setProperty("netbeans.dirs", ide.getPath() + File.pathSeparator + newExtra.getPath()); + + Stamps.main("init"); + + assertNull("Cache invalidated as relative location of clusters changed", + Stamps.getModulesJARs().asByteBuffer("test-cache") + ); + } + + public void testChangeOfClustersIsDetectedInSharedConfig() throws Exception { + clearWorkDir(); + + install = new File(getWorkDir(), "install"); + platform = new File(install, "platform"); + platform.mkdirs(); + new File(platform, ".lastModified").createNewFile(); + ide = new File(install, "ide"); + ide.mkdirs(); + new File(ide, ".lastModified").createNewFile(); + mainCluster = new File(install, "extra"); + mainCluster.mkdirs(); + assertTrue("Extra cluster exists", mainCluster.isDirectory()); + new File(mainCluster, ".lastModified").createNewFile(); + userdir = new File(getWorkDir(), "tmp"); + userdir.mkdirs(); + + System.setProperty("netbeans.home", platform.getPath()); + System.setProperty("netbeans.dirs", ide.getPath()); + // generate the cache to mainCluster directory + System.setProperty("netbeans.user", mainCluster.getPath()); + + + Thread.sleep(500); + long between = System.currentTimeMillis(); + Thread.sleep(500); + + + Stamps.main("init"); + + Stamps.getModulesJARs().scheduleSave(this, "test-cache", false); + Stamps.getModulesJARs().waitFor(true); + int[] arr = { 0 }; + File f = Stamps.getModulesJARs().file("test-cache", arr); + assertNotNull("Cache found", f); + assertEquals("Stamps of caches shall be the same as stamps of .lastModified", + f.lastModified(), Stamps.moduleJARs() + ); + + File lmdir = new File(new File(new File(mainCluster, "var"), "cache"), "lastModified"); + assertTrue(lmdir + " is dir", lmdir.isDirectory()); + lmdir.renameTo(new File(lmdir.getParentFile(), "ignore")); + assertFalse(lmdir + " is no longer dir", lmdir.isDirectory()); + + Thread.sleep(500); + + System.setProperty("netbeans.user", userdir.getPath()); + // use mainCluster as cluster + System.setProperty("netbeans.dirs", mainCluster.getPath() + File.pathSeparator + ide.getPath()); + + Stamps.main("init"); + + assertNull("Cache invalidated set of clusters changed", + Stamps.getModulesJARs().asByteBuffer("test-cache") + ); + } + + @Override + public void flushCaches(DataOutputStream os) throws IOException { + os.write(1); + } + + @Override + public void cacheReady() { + } +} diff -r 7d779ce00a82 o.n.bootstrap/test/unit/src/org/netbeans/StampsIdeLessThanPlatformTest.java --- a/o.n.bootstrap/test/unit/src/org/netbeans/StampsIdeLessThanPlatformTest.java Mon Apr 15 14:03:49 2013 +0200 +++ b/o.n.bootstrap/test/unit/src/org/netbeans/StampsIdeLessThanPlatformTest.java Mon Apr 15 16:47:24 2013 +0200 @@ -132,7 +132,7 @@ String[] seg = line.split("="); assertEquals("There should be one = in the: " + line, 2, seg.length); String s = seg[0]; - if (s.endsWith("home")) { + if (s.endsWith("platform")) { assertEquals("Correct for platform: " + line, "60000", seg[1]); check ++; }