# HG changeset patch
# Parent d330459d6f579f89b9116b0a2869bfbe700034f8
diff --git a/openide.loaders/src/org/openide/loaders/DataShadow.java b/openide.loaders/src/org/openide/loaders/DataShadow.java
--- a/openide.loaders/src/org/openide/loaders/DataShadow.java
+++ b/openide.loaders/src/org/openide/loaders/DataShadow.java
@@ -393,9 +393,13 @@
/**
* Tries to load the original file from a shadow.
* Looks for file contents as well as the originalFile/originalFileSystem attributes.
+ * Attempts to discover locations for migrated files in configuration.
+ *
* @param fileObject a data shadow
* @return the original DataObject
referenced by the shadow
* @throws IOException error during load or broken link
+ *
+ * @see Utilities#translate
*/
protected static DataObject deserialize(FileObject fileObject) throws IOException {
String[] fileAndFileSystem = readOriginalFileAndFileSystem(fileObject);
@@ -411,18 +415,55 @@
String fsname = fileAndFileSystem[1];
if (u != null && u.isAbsolute()) {
target = URLMapper.findFileObject(u.toURL());
+ if (target == null) {
+ FileObject cfgRoot = FileUtil.getConfigRoot();
+ URI cfgURI = cfgRoot.toURI();
+ String prefix = cfgURI.toString();
+ String urlText = u.toString();
+ // try to recover a broken shadow by translation, but only for
+ // configuration
+ if (urlText.startsWith(prefix)) {
+ String rootPathFragment = cfgURI.getRawPath();
+ // get the file part with all the URL escaping, escapes e.g. # and = characters.
+ String cfgPart = u.getRawPath().substring(rootPathFragment.length());
+ String translated = Utilities.translate(cfgPart);
+ if (!translated.equals(cfgPart)) {
+ try {
+ // assume translated value can be added to the prefix, no bad characters allowed
+ URI temp = new URI(prefix + translated);
+ target = URLMapper.findFileObject(temp.toURL());
+ } catch (URISyntaxException ex) {
+ LOG.log(Level.WARNING, "Could not form URI from {0}: {1}", new Object[] { translated, ex });
+ }
+ }
+ }
+ }
} else {
FileSystem fs;
+ boolean sfs;
if (SFS_NAME.equals(fsname)) {
fs = FileUtil.getConfigRoot().getFileSystem();
+ sfs = true;
} else {
// Even if it is specified, we no longer have mounts, so we can no longer find it.
fs = fileObject.getFileSystem();
+ sfs = false;
}
target = fs.findResource(path);
if (target == null && fsname == null) {
+ sfs = true;
target = FileUtil.getConfigFile(path);
}
+
+ // defect #208779 - if target is not found on SFS, try to translate the path:
+ if (sfs && target == null) {
+ // encode the path:
+ String translated = Utilities.translate(path);
+ if (!path.equals(translated)) {
+ // decode translated path back:
+ target = FileUtil.getConfigFile(translated);
+ }
+ }
}
if (target != null) {
return DataObject.find(target);
diff --git a/openide.loaders/test/unit/src/META-INF/netbeans/translate.names b/openide.loaders/test/unit/src/META-INF/netbeans/translate.names
new file mode 100644
--- /dev/null
+++ b/openide.loaders/test/unit/src/META-INF/netbeans/translate.names
@@ -0,0 +1,2 @@
+origFolder/regularFileName.txt.old=origFolder/regularFileName.txt
+dead-file-location.old=origFolder3/moved-here.txt
diff --git a/openide.loaders/test/unit/src/org/netbeans/modules/openide/loaders/DataShadowTranslateTest.java b/openide.loaders/test/unit/src/org/netbeans/modules/openide/loaders/DataShadowTranslateTest.java
new file mode 100644
--- /dev/null
+++ b/openide.loaders/test/unit/src/org/netbeans/modules/openide/loaders/DataShadowTranslateTest.java
@@ -0,0 +1,203 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2012 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 2012 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.openide.loaders;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import org.netbeans.junit.NbTestCase;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileSystem.AtomicAction;
+import org.openide.filesystems.FileUtil;
+import org.openide.loaders.DataObject;
+import org.openide.loaders.DataShadow;
+import org.openide.util.Lookup;
+import org.openide.util.test.MockLookup;
+import org.openide.util.test.TestFileUtils;
+
+/**
+ * See defect #208779 - DataShadows should update themselves iff they could not find
+ * the target.
+ *
+ * @author sdedic
+ */
+public class DataShadowTranslateTest extends NbTestCase {
+
+ public DataShadowTranslateTest(String name) {
+ super(name);
+ }
+
+ /**
+ * Checks that file with just regular characters in name is translated OK
+ * @throws Exception
+ */
+ public void testRegularURI() throws Exception {
+
+ FileObject fo = FileUtil.getConfigRoot();
+
+ FileObject origDir = fo.createFolder("origFolder");
+ FileObject newFile = origDir.createData("regularFileName.txt");
+
+ final FileObject d = fo.createFolder("subfolder");
+ OutputStream ostm = d.createAndOpen("regularShadowURI.shadow");
+ BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(ostm));
+
+ URI uri = newFile.toURI();
+ String urlString = newFile.toURI().toString();
+ bw.write(urlString + ".old");
+ bw.newLine();
+ bw.newLine();
+ bw.close();
+
+ FileObject fob = d.getFileObject("regularShadowURI.shadow");
+ DataObject dd = DataObject.find(fob);
+
+ assertTrue("Shadow must be translated, not broken", dd instanceof DataShadow);
+
+ DataShadow ds = (DataShadow)dd;
+ assertEquals("Shadow's original must be on the translated location", newFile, ds.getOriginal().getPrimaryFile());
+ }
+
+
+ /**
+ * Checks translation on Shadows, which use FS name + path, not URI
+ * @throws Exception
+ */
+ public void testFSNameAndPath() throws Exception {
+ FileObject fo = FileUtil.getConfigRoot();
+
+ FileObject origDir = fo.createFolder("origFolder3");
+
+ // create empty real file with special and non-ASCII chars
+ FileObject newFile = origDir.createData("moved-here.txt");
+
+ // createa a fake file, just to get its URI right:
+ FileObject fake = fo.createData("dead-file-location.old");
+
+ final FileObject d = fo.createFolder("subfolder3");
+ OutputStream ostm = d.createAndOpen("regularShadowURI.shadow");
+ BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(ostm));
+
+ bw.write(fake.getPath());
+ bw.newLine();
+ bw.write(fake.getFileSystem().getSystemName());
+ bw.newLine();
+
+ fake.delete();
+
+ bw.close();
+
+ FileObject fob = d.getFileObject("regularShadowURI.shadow");
+ DataObject dd = DataObject.find(fob);
+
+ assertTrue("Shadow must be translated, not broken", dd instanceof DataShadow);
+
+ DataShadow ds = (DataShadow)dd;
+ assertEquals("Shadow's original must be on the translated location", newFile, ds.getOriginal().getPrimaryFile());
+ }
+
+ /**
+ * Checks that DataShadows to regular (non-SFS) files are not translated
+ * even if a translation is defined for their path
+ *
+ * @throws Exception
+ */
+ public void testNonSFSUriNotAffected() throws Exception {
+ File wd = getWorkDir();
+
+ clearWorkDir();
+
+ FileObject origDir = FileUtil.toFileObject(wd);
+
+ FileObject dirWithSpace = origDir.createFolder("Space Dir");
+ FileObject newFile = dirWithSpace.createData("testFile.txt");
+
+
+ File subDir = new File(wd, "translate");
+
+ subDir.mkdirs();
+
+ File metaTranslate = new File(subDir, "META-INF/netbeans");
+ metaTranslate.mkdirs();
+
+ String workPath = newFile.toURI().getRawPath();
+ FileWriter wr = new FileWriter(new File(metaTranslate, "translate.names"));
+ BufferedWriter bw = new BufferedWriter(wr);
+
+ bw.write(workPath.substring(1) + "/testFile.txt=" + workPath.substring(1) + "/moved/testFile.txt");
+ bw.close();
+
+
+ FileObject fo = FileUtil.toFileObject(wd);
+
+ ClassLoader orig = Lookup.getDefault().lookup(ClassLoader.class);
+
+ ClassLoader my = new URLClassLoader(new URL[] {
+ subDir.toURL()
+ }, orig);
+
+ MockLookup.setInstances(my);
+
+
+ FileObject cfgRoot = FileUtil.getConfigRoot();
+
+
+ OutputStream ostm = cfgRoot.createAndOpen("nonSFSFile.shadow");
+
+ bw = new BufferedWriter(new OutputStreamWriter(ostm));
+
+ bw.write(newFile.toURI().toString());
+ bw.newLine();
+ bw.newLine();
+
+ newFile.delete();
+
+ bw.close();
+
+ FileObject fob = cfgRoot.getFileObject("nonSFSFile.shadow");
+ DataObject dd = DataObject.find(fob);
+
+ assertFalse("Shadow must be still broken", dd instanceof DataShadow);
+ }
+}
+
diff --git a/openide.util/src/org/openide/util/Utilities.java b/openide.util/src/org/openide/util/Utilities.java
--- a/openide.util/src/org/openide/util/Utilities.java
+++ b/openide.util/src/org/openide/util/Utilities.java
@@ -2468,7 +2468,8 @@
}
/** Provides support for parts of the system that deal with classnames
- * (use Class.forName
, NbObjectInputStream
, etc.).
+ * (use Class.forName
, NbObjectInputStream
, etc.) or filenames
+ * in layers.
*
* Often class names (especially package names) changes during lifecycle * of a module. When some piece of the system stores the name of a class @@ -2513,8 +2514,21 @@ * className is not listed as one that is to be renamed, the returned * string == className, if the className is registered to be renamed * than the className != returned value, even in a case when className.equals (retValue) + *
+ * Similar behaviour applies to filenames provided by layers (system filesystem). Filenames + * can be also translated to adapt to location changes e.g. in action registrations. Note that + * no spaces or special characters are allowed in both translated filenames or translation + * results. Filenames must conform to regexp {@code ^[/a-zA-Z0-9$_.+-]+$}. Keys and values are treated + * as paths from fs root. + * + * + * Example of file path translation (action registration file has moved): + *+ * # registration ID has changed + * Actions/Refactoring/RefactoringWhereUsed.instance=Actions/Refactoring/org-netbeans-modules-refactoring-api-ui-WhereUsedAction.instance + ** - * @param className fully qualified name of a class to translate + * @param className fully qualified name of a class, or file path to translate * @return new name of the class according to renaming rules. */ public static String translate(final String className) {